1 | #include "mupdf/fitz.h" |
2 | #include "html-imp.h" |
3 | |
4 | #include <string.h> |
5 | |
6 | struct lexbuf |
7 | { |
8 | fz_context *ctx; |
9 | fz_pool *pool; |
10 | const unsigned char *s; |
11 | const char *file; |
12 | int line; |
13 | int lookahead; |
14 | int c; |
15 | int string_len; |
16 | char string[1024]; |
17 | }; |
18 | |
19 | static fz_css_value *parse_expr(struct lexbuf *buf); |
20 | static fz_css_selector *parse_selector(struct lexbuf *buf); |
21 | |
22 | FZ_NORETURN static void fz_css_error(struct lexbuf *buf, const char *msg) |
23 | { |
24 | fz_throw(buf->ctx, FZ_ERROR_SYNTAX, "css syntax error: %s (%s:%d)" , msg, buf->file, buf->line); |
25 | } |
26 | |
27 | fz_css *fz_new_css(fz_context *ctx) |
28 | { |
29 | fz_pool *pool = fz_new_pool(ctx); |
30 | fz_css *css = NULL; |
31 | |
32 | fz_try(ctx) |
33 | { |
34 | css = fz_pool_alloc(ctx, pool, sizeof *css); |
35 | css->pool = pool; |
36 | css->rule = NULL; |
37 | } |
38 | fz_catch(ctx) |
39 | { |
40 | fz_drop_pool(ctx, pool); |
41 | fz_rethrow(ctx); |
42 | } |
43 | |
44 | return css; |
45 | } |
46 | |
47 | void fz_drop_css(fz_context *ctx, fz_css *css) |
48 | { |
49 | if (css) |
50 | fz_drop_pool(ctx, css->pool); |
51 | } |
52 | |
53 | static fz_css_rule *fz_new_css_rule(fz_context *ctx, fz_pool *pool, fz_css_selector *selector, fz_css_property *declaration) |
54 | { |
55 | fz_css_rule *rule = fz_pool_alloc(ctx, pool, sizeof *rule); |
56 | rule->selector = selector; |
57 | rule->declaration = declaration; |
58 | rule->next = NULL; |
59 | return rule; |
60 | } |
61 | |
62 | static fz_css_selector *fz_new_css_selector(fz_context *ctx, fz_pool *pool, const char *name) |
63 | { |
64 | fz_css_selector *sel = fz_pool_alloc(ctx, pool, sizeof *sel); |
65 | sel->name = name ? fz_pool_strdup(ctx, pool, name) : NULL; |
66 | sel->combine = 0; |
67 | sel->cond = NULL; |
68 | sel->left = NULL; |
69 | sel->right = NULL; |
70 | sel->next = NULL; |
71 | return sel; |
72 | } |
73 | |
74 | static fz_css_condition *fz_new_css_condition(fz_context *ctx, fz_pool *pool, int type, const char *key, const char *val) |
75 | { |
76 | fz_css_condition *cond = fz_pool_alloc(ctx, pool, sizeof *cond); |
77 | cond->type = type; |
78 | cond->key = key ? fz_pool_strdup(ctx, pool, key) : NULL; |
79 | cond->val = val ? fz_pool_strdup(ctx, pool, val) : NULL; |
80 | cond->next = NULL; |
81 | return cond; |
82 | } |
83 | |
84 | static fz_css_property *fz_new_css_property(fz_context *ctx, fz_pool *pool, const char *name, fz_css_value *value, int spec) |
85 | { |
86 | fz_css_property *prop = fz_pool_alloc(ctx, pool, sizeof *prop); |
87 | prop->name = fz_pool_strdup(ctx, pool, name); |
88 | prop->value = value; |
89 | prop->spec = spec; |
90 | prop->important = 0; |
91 | prop->next = NULL; |
92 | return prop; |
93 | } |
94 | |
95 | static fz_css_value *fz_new_css_value_x(fz_context *ctx, fz_pool *pool, int type) |
96 | { |
97 | fz_css_value *val = fz_pool_alloc(ctx, pool, sizeof *val); |
98 | val->type = type; |
99 | val->data = NULL; |
100 | val->args = NULL; |
101 | val->next = NULL; |
102 | return val; |
103 | } |
104 | |
105 | static fz_css_value *fz_new_css_value(fz_context *ctx, fz_pool *pool, int type, const char *data) |
106 | { |
107 | fz_css_value *val = fz_pool_alloc(ctx, pool, sizeof *val); |
108 | val->type = type; |
109 | val->data = fz_pool_strdup(ctx, pool, data); |
110 | val->args = NULL; |
111 | val->next = NULL; |
112 | return val; |
113 | } |
114 | |
115 | static void css_lex_next(struct lexbuf *buf) |
116 | { |
117 | buf->c = *(buf->s++); |
118 | if (buf->c == '\n') |
119 | ++buf->line; |
120 | } |
121 | |
122 | static void css_lex_init(fz_context *ctx, struct lexbuf *buf, fz_pool *pool, const char *s, const char *file) |
123 | { |
124 | buf->ctx = ctx; |
125 | buf->pool = pool; |
126 | buf->s = (const unsigned char *)s; |
127 | buf->c = 0; |
128 | buf->file = file; |
129 | buf->line = 1; |
130 | css_lex_next(buf); |
131 | |
132 | buf->string_len = 0; |
133 | } |
134 | |
135 | static inline int iswhite(int c) |
136 | { |
137 | return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\f'; |
138 | } |
139 | |
140 | static int isnmstart(int c) |
141 | { |
142 | return c == '\\' || c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || |
143 | (c >= 128 && c <= 255); |
144 | } |
145 | |
146 | static int isnmchar(int c) |
147 | { |
148 | return c == '\\' || c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || |
149 | (c >= '0' && c <= '9') || c == '-' || (c >= 128 && c <= 255); |
150 | } |
151 | |
152 | static void css_push_char(struct lexbuf *buf, int c) |
153 | { |
154 | if (buf->string_len + 1 >= nelem(buf->string)) |
155 | fz_css_error(buf, "token too long" ); |
156 | buf->string[buf->string_len++] = c; |
157 | } |
158 | |
159 | static int css_lex_accept(struct lexbuf *buf, int t) |
160 | { |
161 | if (buf->c == t) |
162 | { |
163 | css_lex_next(buf); |
164 | return 1; |
165 | } |
166 | return 0; |
167 | } |
168 | |
169 | static void css_lex_expect(struct lexbuf *buf, int t) |
170 | { |
171 | if (!css_lex_accept(buf, t)) |
172 | fz_css_error(buf, "unexpected character" ); |
173 | } |
174 | |
175 | static int css_lex_number(struct lexbuf *buf) |
176 | { |
177 | while (buf->c >= '0' && buf->c <= '9') |
178 | { |
179 | css_push_char(buf, buf->c); |
180 | css_lex_next(buf); |
181 | } |
182 | |
183 | if (css_lex_accept(buf, '.')) |
184 | { |
185 | css_push_char(buf, '.'); |
186 | while (buf->c >= '0' && buf->c <= '9') |
187 | { |
188 | css_push_char(buf, buf->c); |
189 | css_lex_next(buf); |
190 | } |
191 | } |
192 | |
193 | if (css_lex_accept(buf, '%')) |
194 | { |
195 | css_push_char(buf, '%'); |
196 | css_push_char(buf, 0); |
197 | return CSS_PERCENT; |
198 | } |
199 | |
200 | if (isnmstart(buf->c)) |
201 | { |
202 | css_push_char(buf, buf->c); |
203 | css_lex_next(buf); |
204 | while (isnmchar(buf->c)) |
205 | { |
206 | css_push_char(buf, buf->c); |
207 | css_lex_next(buf); |
208 | } |
209 | css_push_char(buf, 0); |
210 | return CSS_LENGTH; |
211 | } |
212 | |
213 | css_push_char(buf, 0); |
214 | return CSS_NUMBER; |
215 | } |
216 | |
217 | static int css_lex_keyword(struct lexbuf *buf) |
218 | { |
219 | while (isnmchar(buf->c)) |
220 | { |
221 | css_push_char(buf, buf->c); |
222 | css_lex_next(buf); |
223 | } |
224 | css_push_char(buf, 0); |
225 | return CSS_KEYWORD; |
226 | } |
227 | |
228 | static int css_lex_hash(struct lexbuf *buf) |
229 | { |
230 | while (isnmchar(buf->c)) |
231 | { |
232 | css_push_char(buf, buf->c); |
233 | css_lex_next(buf); |
234 | } |
235 | css_push_char(buf, 0); |
236 | return CSS_HASH; |
237 | } |
238 | |
239 | static int css_lex_string(struct lexbuf *buf, int q) |
240 | { |
241 | while (buf->c && buf->c != q) |
242 | { |
243 | if (css_lex_accept(buf, '\\')) |
244 | { |
245 | if (css_lex_accept(buf, 'n')) |
246 | css_push_char(buf, '\n'); |
247 | else if (css_lex_accept(buf, 'r')) |
248 | css_push_char(buf, '\r'); |
249 | else if (css_lex_accept(buf, 'f')) |
250 | css_push_char(buf, '\f'); |
251 | else if (css_lex_accept(buf, '\f')) |
252 | /* line continuation */ ; |
253 | else if (css_lex_accept(buf, '\n')) |
254 | /* line continuation */ ; |
255 | else if (css_lex_accept(buf, '\r')) |
256 | css_lex_accept(buf, '\n'); |
257 | else |
258 | { |
259 | css_push_char(buf, buf->c); |
260 | css_lex_next(buf); |
261 | } |
262 | } |
263 | else |
264 | { |
265 | css_push_char(buf, buf->c); |
266 | css_lex_next(buf); |
267 | } |
268 | } |
269 | css_lex_expect(buf, q); |
270 | css_push_char(buf, 0); |
271 | return CSS_STRING; |
272 | } |
273 | |
274 | static void css_lex_uri(struct lexbuf *buf) |
275 | { |
276 | while (buf->c && buf->c != ')' && !iswhite(buf->c)) |
277 | { |
278 | if (css_lex_accept(buf, '\\')) |
279 | { |
280 | if (css_lex_accept(buf, 'n')) |
281 | css_push_char(buf, '\n'); |
282 | else if (css_lex_accept(buf, 'r')) |
283 | css_push_char(buf, '\r'); |
284 | else if (css_lex_accept(buf, 'f')) |
285 | css_push_char(buf, '\f'); |
286 | else |
287 | { |
288 | css_push_char(buf, buf->c); |
289 | css_lex_next(buf); |
290 | } |
291 | } |
292 | else if (buf->c == '!' || buf->c == '#' || buf->c == '$' || buf->c == '%' || buf->c == '&' || |
293 | (buf->c >= '*' && buf->c <= '[') || |
294 | (buf->c >= ']' && buf->c <= '~') || |
295 | buf->c > 159) |
296 | { |
297 | css_push_char(buf, buf->c); |
298 | css_lex_next(buf); |
299 | } |
300 | else |
301 | fz_css_error(buf, "unexpected character in url" ); |
302 | } |
303 | css_push_char(buf, 0); |
304 | } |
305 | |
306 | static int css_lex(struct lexbuf *buf) |
307 | { |
308 | int t; |
309 | |
310 | // TODO: keyword escape sequences |
311 | |
312 | buf->string_len = 0; |
313 | |
314 | restart: |
315 | if (buf->c == 0) |
316 | return EOF; |
317 | |
318 | if (iswhite(buf->c)) |
319 | { |
320 | while (iswhite(buf->c)) |
321 | css_lex_next(buf); |
322 | return ' '; |
323 | } |
324 | |
325 | if (css_lex_accept(buf, '/')) |
326 | { |
327 | if (css_lex_accept(buf, '*')) |
328 | { |
329 | while (buf->c) |
330 | { |
331 | if (css_lex_accept(buf, '*')) |
332 | { |
333 | while (buf->c == '*') |
334 | css_lex_next(buf); |
335 | if (css_lex_accept(buf, '/')) |
336 | goto restart; |
337 | } |
338 | css_lex_next(buf); |
339 | } |
340 | fz_css_error(buf, "unterminated comment" ); |
341 | } |
342 | return '/'; |
343 | } |
344 | |
345 | if (css_lex_accept(buf, '<')) |
346 | { |
347 | if (css_lex_accept(buf, '!')) |
348 | { |
349 | css_lex_expect(buf, '-'); |
350 | css_lex_expect(buf, '-'); |
351 | goto restart; /* ignore CDO */ |
352 | } |
353 | return '<'; |
354 | } |
355 | |
356 | if (css_lex_accept(buf, '-')) |
357 | { |
358 | if (css_lex_accept(buf, '-')) |
359 | { |
360 | css_lex_expect(buf, '>'); |
361 | goto restart; /* ignore CDC */ |
362 | } |
363 | if (isnmstart(buf->c)) |
364 | { |
365 | css_push_char(buf, '-'); |
366 | return css_lex_keyword(buf); |
367 | } |
368 | return '-'; |
369 | } |
370 | |
371 | if (css_lex_accept(buf, '.')) |
372 | { |
373 | if (buf->c >= '0' && buf->c <= '9') |
374 | { |
375 | css_push_char(buf, '.'); |
376 | return css_lex_number(buf); |
377 | } |
378 | return '.'; |
379 | } |
380 | |
381 | if (css_lex_accept(buf, '#')) |
382 | { |
383 | if (isnmchar(buf->c)) |
384 | return css_lex_hash(buf); |
385 | return '#'; |
386 | } |
387 | |
388 | if (css_lex_accept(buf, '"')) |
389 | return css_lex_string(buf, '"'); |
390 | if (css_lex_accept(buf, '\'')) |
391 | return css_lex_string(buf, '\''); |
392 | |
393 | if (buf->c >= '0' && buf->c <= '9') |
394 | return css_lex_number(buf); |
395 | |
396 | if (css_lex_accept(buf, 'u')) |
397 | { |
398 | if (css_lex_accept(buf, 'r')) |
399 | { |
400 | if (css_lex_accept(buf, 'l')) |
401 | { |
402 | if (css_lex_accept(buf, '(')) |
403 | { |
404 | while (iswhite(buf->c)) |
405 | css_lex_next(buf); |
406 | if (css_lex_accept(buf, '"')) |
407 | css_lex_string(buf, '"'); |
408 | else if (css_lex_accept(buf, '\'')) |
409 | css_lex_string(buf, '\''); |
410 | else |
411 | css_lex_uri(buf); |
412 | while (iswhite(buf->c)) |
413 | css_lex_next(buf); |
414 | css_lex_expect(buf, ')'); |
415 | return CSS_URI; |
416 | } |
417 | css_push_char(buf, 'u'); |
418 | css_push_char(buf, 'r'); |
419 | css_push_char(buf, 'l'); |
420 | return css_lex_keyword(buf); |
421 | } |
422 | css_push_char(buf, 'u'); |
423 | css_push_char(buf, 'r'); |
424 | return css_lex_keyword(buf); |
425 | } |
426 | css_push_char(buf, 'u'); |
427 | return css_lex_keyword(buf); |
428 | } |
429 | |
430 | if (isnmstart(buf->c)) |
431 | { |
432 | css_push_char(buf, buf->c); |
433 | css_lex_next(buf); |
434 | return css_lex_keyword(buf); |
435 | } |
436 | |
437 | t = buf->c; |
438 | css_lex_next(buf); |
439 | return t; |
440 | } |
441 | |
442 | static void next(struct lexbuf *buf) |
443 | { |
444 | buf->lookahead = css_lex(buf); |
445 | } |
446 | |
447 | static int accept(struct lexbuf *buf, int t) |
448 | { |
449 | if (buf->lookahead == t) |
450 | { |
451 | next(buf); |
452 | return 1; |
453 | } |
454 | return 0; |
455 | } |
456 | |
457 | static void expect(struct lexbuf *buf, int t) |
458 | { |
459 | if (accept(buf, t)) |
460 | return; |
461 | fz_css_error(buf, "unexpected token" ); |
462 | } |
463 | |
464 | static void white(struct lexbuf *buf) |
465 | { |
466 | while (buf->lookahead == ' ') |
467 | next(buf); |
468 | } |
469 | |
470 | static int iscond(int t) |
471 | { |
472 | return t == ':' || t == '.' || t == '[' || t == CSS_HASH; |
473 | } |
474 | |
475 | static fz_css_value *parse_term(struct lexbuf *buf) |
476 | { |
477 | fz_css_value *v; |
478 | |
479 | if (buf->lookahead == '+' || buf->lookahead == '-') |
480 | { |
481 | float sign = buf->lookahead == '-' ? -1 : 1; |
482 | next(buf); |
483 | if (buf->lookahead != CSS_NUMBER && buf->lookahead != CSS_LENGTH && buf->lookahead != CSS_PERCENT) |
484 | fz_css_error(buf, "expected number" ); |
485 | if (sign < 0) |
486 | { |
487 | v = fz_new_css_value_x(buf->ctx, buf->pool, buf->lookahead); |
488 | v->data = fz_pool_alloc(buf->ctx, buf->pool, strlen(buf->string) + 2); |
489 | v->data[0] = '-'; |
490 | strcpy(v->data + 1, buf->string); |
491 | } |
492 | else |
493 | { |
494 | v = fz_new_css_value(buf->ctx, buf->pool, buf->lookahead, buf->string); |
495 | } |
496 | next(buf); |
497 | white(buf); |
498 | return v; |
499 | } |
500 | |
501 | if (buf->lookahead == CSS_KEYWORD) |
502 | { |
503 | v = fz_new_css_value(buf->ctx, buf->pool, CSS_KEYWORD, buf->string); |
504 | next(buf); |
505 | if (accept(buf, '(')) |
506 | { |
507 | white(buf); |
508 | v->type = '('; |
509 | v->args = parse_expr(buf); |
510 | expect(buf, ')'); |
511 | } |
512 | white(buf); |
513 | return v; |
514 | } |
515 | |
516 | switch (buf->lookahead) |
517 | { |
518 | case CSS_HASH: |
519 | case CSS_STRING: |
520 | case CSS_URI: |
521 | case CSS_NUMBER: |
522 | case CSS_LENGTH: |
523 | case CSS_PERCENT: |
524 | v = fz_new_css_value(buf->ctx, buf->pool, buf->lookahead, buf->string); |
525 | next(buf); |
526 | white(buf); |
527 | return v; |
528 | } |
529 | |
530 | fz_css_error(buf, "expected value" ); |
531 | } |
532 | |
533 | static fz_css_value *parse_expr(struct lexbuf *buf) |
534 | { |
535 | fz_css_value *head, *tail; |
536 | |
537 | head = tail = parse_term(buf); |
538 | |
539 | while (buf->lookahead != '}' && buf->lookahead != ';' && buf->lookahead != '!' && |
540 | buf->lookahead != ')' && buf->lookahead != EOF) |
541 | { |
542 | if (accept(buf, ',')) |
543 | { |
544 | white(buf); |
545 | tail = tail->next = fz_new_css_value(buf->ctx, buf->pool, ',', "," ); |
546 | tail = tail->next = parse_term(buf); |
547 | } |
548 | else if (accept(buf, '/')) |
549 | { |
550 | white(buf); |
551 | tail = tail->next = fz_new_css_value(buf->ctx, buf->pool, '/', "/" ); |
552 | tail = tail->next = parse_term(buf); |
553 | } |
554 | else |
555 | { |
556 | tail = tail->next = parse_term(buf); |
557 | } |
558 | } |
559 | |
560 | return head; |
561 | } |
562 | |
563 | static fz_css_property *parse_declaration(struct lexbuf *buf) |
564 | { |
565 | fz_css_property *p; |
566 | |
567 | if (buf->lookahead != CSS_KEYWORD) |
568 | fz_css_error(buf, "expected keyword in property" ); |
569 | p = fz_new_css_property(buf->ctx, buf->pool, buf->string, NULL, 0); |
570 | next(buf); |
571 | |
572 | white(buf); |
573 | expect(buf, ':'); |
574 | white(buf); |
575 | |
576 | p->value = parse_expr(buf); |
577 | |
578 | /* !important */ |
579 | if (accept(buf, '!')) |
580 | { |
581 | white(buf); |
582 | if (buf->lookahead != CSS_KEYWORD || strcmp(buf->string, "important" )) |
583 | fz_css_error(buf, "expected keyword 'important' after '!'" ); |
584 | p->important = 1; |
585 | next(buf); |
586 | white(buf); |
587 | } |
588 | |
589 | return p; |
590 | } |
591 | |
592 | static fz_css_property *parse_declaration_list(struct lexbuf *buf) |
593 | { |
594 | fz_css_property *head, *tail; |
595 | |
596 | white(buf); |
597 | |
598 | if (buf->lookahead == '}' || buf->lookahead == EOF) |
599 | return NULL; |
600 | |
601 | head = tail = parse_declaration(buf); |
602 | |
603 | while (accept(buf, ';')) |
604 | { |
605 | white(buf); |
606 | |
607 | if (buf->lookahead != '}' && buf->lookahead != ';' && buf->lookahead != EOF) |
608 | { |
609 | tail = tail->next = parse_declaration(buf); |
610 | } |
611 | } |
612 | |
613 | return head; |
614 | } |
615 | |
616 | static char *parse_attrib_value(struct lexbuf *buf) |
617 | { |
618 | char *s; |
619 | |
620 | if (buf->lookahead == CSS_KEYWORD || buf->lookahead == CSS_STRING) |
621 | { |
622 | s = fz_pool_strdup(buf->ctx, buf->pool, buf->string); |
623 | next(buf); |
624 | white(buf); |
625 | return s; |
626 | } |
627 | |
628 | fz_css_error(buf, "expected attribute value" ); |
629 | } |
630 | |
631 | static fz_css_condition *parse_condition(struct lexbuf *buf) |
632 | { |
633 | fz_css_condition *c; |
634 | |
635 | if (accept(buf, ':')) |
636 | { |
637 | accept(buf, ':'); /* swallow css3 :: syntax and pretend it's a normal pseudo-class */ |
638 | if (buf->lookahead != CSS_KEYWORD) |
639 | fz_css_error(buf, "expected keyword after ':'" ); |
640 | c = fz_new_css_condition(buf->ctx, buf->pool, ':', "pseudo" , buf->string); |
641 | next(buf); |
642 | if (accept(buf, '(')) |
643 | { |
644 | white(buf); |
645 | if (accept(buf, CSS_KEYWORD)) |
646 | white(buf); |
647 | expect(buf, ')'); |
648 | } |
649 | return c; |
650 | } |
651 | |
652 | if (accept(buf, '.')) |
653 | { |
654 | if (buf->lookahead != CSS_KEYWORD) |
655 | fz_css_error(buf, "expected keyword after '.'" ); |
656 | c = fz_new_css_condition(buf->ctx, buf->pool, '.', "class" , buf->string); |
657 | next(buf); |
658 | return c; |
659 | } |
660 | |
661 | if (accept(buf, '[')) |
662 | { |
663 | white(buf); |
664 | |
665 | if (buf->lookahead != CSS_KEYWORD) |
666 | fz_css_error(buf, "expected keyword after '['" ); |
667 | c = fz_new_css_condition(buf->ctx, buf->pool, '[', buf->string, NULL); |
668 | next(buf); |
669 | |
670 | white(buf); |
671 | |
672 | if (accept(buf, '=')) |
673 | { |
674 | c->type = '='; |
675 | c->val = parse_attrib_value(buf); |
676 | } |
677 | else if (accept(buf, '|')) |
678 | { |
679 | expect(buf, '='); |
680 | c->type = '|'; |
681 | c->val = parse_attrib_value(buf); |
682 | } |
683 | else if (accept(buf, '~')) |
684 | { |
685 | expect(buf, '='); |
686 | c->type = '~'; |
687 | c->val = parse_attrib_value(buf); |
688 | } |
689 | |
690 | expect(buf, ']'); |
691 | |
692 | return c; |
693 | } |
694 | |
695 | if (buf->lookahead == CSS_HASH) |
696 | { |
697 | c = fz_new_css_condition(buf->ctx, buf->pool, '#', "id" , buf->string); |
698 | next(buf); |
699 | return c; |
700 | } |
701 | |
702 | fz_css_error(buf, "expected condition" ); |
703 | } |
704 | |
705 | static fz_css_condition *parse_condition_list(struct lexbuf *buf) |
706 | { |
707 | fz_css_condition *head, *tail; |
708 | |
709 | head = tail = parse_condition(buf); |
710 | while (iscond(buf->lookahead)) |
711 | { |
712 | tail = tail->next = parse_condition(buf); |
713 | } |
714 | return head; |
715 | } |
716 | |
717 | static fz_css_selector *parse_simple_selector(struct lexbuf *buf) |
718 | { |
719 | fz_css_selector *s; |
720 | |
721 | if (accept(buf, '*')) |
722 | { |
723 | s = fz_new_css_selector(buf->ctx, buf->pool, NULL); |
724 | if (iscond(buf->lookahead)) |
725 | s->cond = parse_condition_list(buf); |
726 | return s; |
727 | } |
728 | else if (buf->lookahead == CSS_KEYWORD) |
729 | { |
730 | s = fz_new_css_selector(buf->ctx, buf->pool, buf->string); |
731 | next(buf); |
732 | if (iscond(buf->lookahead)) |
733 | s->cond = parse_condition_list(buf); |
734 | return s; |
735 | } |
736 | else if (iscond(buf->lookahead)) |
737 | { |
738 | s = fz_new_css_selector(buf->ctx, buf->pool, NULL); |
739 | s->cond = parse_condition_list(buf); |
740 | return s; |
741 | } |
742 | |
743 | fz_css_error(buf, "expected selector" ); |
744 | } |
745 | |
746 | static fz_css_selector *parse_combinator(struct lexbuf *buf, int c, fz_css_selector *a) |
747 | { |
748 | fz_css_selector *sel, *b; |
749 | white(buf); |
750 | b = parse_simple_selector(buf); |
751 | sel = fz_new_css_selector(buf->ctx, buf->pool, NULL); |
752 | sel->combine = c; |
753 | sel->left = a; |
754 | sel->right = b; |
755 | return sel; |
756 | } |
757 | |
758 | static fz_css_selector *parse_selector(struct lexbuf *buf) |
759 | { |
760 | fz_css_selector *sel = parse_simple_selector(buf); |
761 | for (;;) |
762 | { |
763 | if (accept(buf, ' ')) |
764 | { |
765 | if (accept(buf, '+')) |
766 | sel = parse_combinator(buf, '+', sel); |
767 | else if (accept(buf, '>')) |
768 | sel = parse_combinator(buf, '>', sel); |
769 | else if (buf->lookahead != ',' && buf->lookahead != '{' && buf->lookahead != EOF) |
770 | sel = parse_combinator(buf, ' ', sel); |
771 | else |
772 | break; |
773 | } |
774 | else if (accept(buf, '+')) |
775 | sel = parse_combinator(buf, '+', sel); |
776 | else if (accept(buf, '>')) |
777 | sel = parse_combinator(buf, '>', sel); |
778 | else |
779 | break; |
780 | } |
781 | return sel; |
782 | } |
783 | |
784 | static fz_css_selector *parse_selector_list(struct lexbuf *buf) |
785 | { |
786 | fz_css_selector *head, *tail; |
787 | |
788 | head = tail = parse_selector(buf); |
789 | while (accept(buf, ',')) |
790 | { |
791 | white(buf); |
792 | tail = tail->next = parse_selector(buf); |
793 | } |
794 | return head; |
795 | } |
796 | |
797 | static fz_css_rule *parse_ruleset(struct lexbuf *buf) |
798 | { |
799 | fz_css_selector *s = NULL; |
800 | fz_css_property *p = NULL; |
801 | |
802 | fz_try(buf->ctx) |
803 | { |
804 | s = parse_selector_list(buf); |
805 | expect(buf, '{'); |
806 | p = parse_declaration_list(buf); |
807 | expect(buf, '}'); |
808 | white(buf); |
809 | } |
810 | fz_catch(buf->ctx) |
811 | { |
812 | if (fz_caught(buf->ctx) != FZ_ERROR_SYNTAX) |
813 | fz_rethrow(buf->ctx); |
814 | while (buf->lookahead != EOF) |
815 | { |
816 | if (accept(buf, '}')) |
817 | { |
818 | white(buf); |
819 | break; |
820 | } |
821 | next(buf); |
822 | } |
823 | return NULL; |
824 | } |
825 | |
826 | return fz_new_css_rule(buf->ctx, buf->pool, s, p); |
827 | } |
828 | |
829 | static fz_css_rule *parse_at_page(struct lexbuf *buf) |
830 | { |
831 | fz_css_selector *s = NULL; |
832 | fz_css_property *p = NULL; |
833 | |
834 | white(buf); |
835 | if (accept(buf, ':')) |
836 | { |
837 | expect(buf, CSS_KEYWORD); |
838 | white(buf); |
839 | } |
840 | expect(buf, '{'); |
841 | p = parse_declaration_list(buf); |
842 | expect(buf, '}'); |
843 | white(buf); |
844 | |
845 | s = fz_new_css_selector(buf->ctx, buf->pool, "@page" ); |
846 | return fz_new_css_rule(buf->ctx, buf->pool, s, p); |
847 | } |
848 | |
849 | static fz_css_rule *parse_at_font_face(struct lexbuf *buf) |
850 | { |
851 | fz_css_selector *s = NULL; |
852 | fz_css_property *p = NULL; |
853 | |
854 | white(buf); |
855 | expect(buf, '{'); |
856 | p = parse_declaration_list(buf); |
857 | expect(buf, '}'); |
858 | white(buf); |
859 | |
860 | s = fz_new_css_selector(buf->ctx, buf->pool, "@font-face" ); |
861 | return fz_new_css_rule(buf->ctx, buf->pool, s, p); |
862 | } |
863 | |
864 | static void parse_at_rule(struct lexbuf *buf) |
865 | { |
866 | expect(buf, CSS_KEYWORD); |
867 | |
868 | /* skip until '{' or ';' */ |
869 | while (buf->lookahead != EOF) |
870 | { |
871 | if (accept(buf, ';')) |
872 | { |
873 | white(buf); |
874 | return; |
875 | } |
876 | if (accept(buf, '{')) |
877 | { |
878 | int depth = 1; |
879 | while (buf->lookahead != EOF && depth > 0) |
880 | { |
881 | if (accept(buf, '{')) |
882 | ++depth; |
883 | else if (accept(buf, '}')) |
884 | --depth; |
885 | else |
886 | next(buf); |
887 | } |
888 | white(buf); |
889 | return; |
890 | } |
891 | next(buf); |
892 | } |
893 | } |
894 | |
895 | static fz_css_rule *parse_stylesheet(struct lexbuf *buf, fz_css_rule *chain) |
896 | { |
897 | fz_css_rule *rule, **nextp, *tail; |
898 | |
899 | tail = chain; |
900 | if (tail) |
901 | { |
902 | while (tail->next) |
903 | tail = tail->next; |
904 | nextp = &tail->next; |
905 | } |
906 | else |
907 | { |
908 | nextp = &tail; |
909 | } |
910 | |
911 | white(buf); |
912 | |
913 | while (buf->lookahead != EOF) |
914 | { |
915 | if (accept(buf, '@')) |
916 | { |
917 | if (buf->lookahead == CSS_KEYWORD && !strcmp(buf->string, "page" )) |
918 | { |
919 | next(buf); |
920 | rule = *nextp = parse_at_page(buf); |
921 | nextp = &rule->next; |
922 | } |
923 | else if (buf->lookahead == CSS_KEYWORD && !strcmp(buf->string, "font-face" )) |
924 | { |
925 | next(buf); |
926 | rule = *nextp = parse_at_font_face(buf); |
927 | nextp = &rule->next; |
928 | } |
929 | else |
930 | { |
931 | parse_at_rule(buf); |
932 | } |
933 | } |
934 | else |
935 | { |
936 | fz_css_rule *x = parse_ruleset(buf); |
937 | if (x) |
938 | { |
939 | rule = *nextp = x; |
940 | nextp = &rule->next; |
941 | } |
942 | } |
943 | white(buf); |
944 | } |
945 | |
946 | return chain ? chain : tail; |
947 | } |
948 | |
949 | fz_css_property *fz_parse_css_properties(fz_context *ctx, fz_pool *pool, const char *source) |
950 | { |
951 | struct lexbuf buf; |
952 | css_lex_init(ctx, &buf, pool, source, "<inline>" ); |
953 | next(&buf); |
954 | return parse_declaration_list(&buf); |
955 | } |
956 | |
957 | void fz_parse_css(fz_context *ctx, fz_css *css, const char *source, const char *file) |
958 | { |
959 | struct lexbuf buf; |
960 | css_lex_init(ctx, &buf, css->pool, source, file); |
961 | next(&buf); |
962 | css->rule = parse_stylesheet(&buf, css->rule); |
963 | } |
964 | |