]> gitweb.michael.orlitzky.com - libsvgtiny.git/blob - src/svgtiny_css.c
src/svgtiny_css.c: implement node_has_attribute_equal() select handler
[libsvgtiny.git] / src / svgtiny_css.c
1 #include <libcss/libcss.h>
2
3 #include "svgtiny.h"
4 #include "svgtiny_internal.h"
5
6 static css_error node_name(void *pw, void *node, css_qname *qname);
7 static css_error node_classes(void *pw, void *node,
8 lwc_string ***classes, uint32_t *n_classes);
9 static css_error node_id(void *pw, void *node, lwc_string **id);
10 static css_error named_parent_node(void *pw, void *node,
11 const css_qname *qname, void **parent);
12 static css_error named_sibling_node(void *pw, void *node,
13 const css_qname *qname, void **sibling);
14 static css_error named_generic_sibling_node(void *pw, void *node,
15 const css_qname *qname, void **sibling);
16 static css_error parent_node(void *pw, void *node, void **parent);
17 static css_error sibling_node(void *pw, void *node, void **sibling);
18 static css_error node_has_name(void *pw, void *node,
19 const css_qname *qname, bool *match);
20 static css_error node_has_class(void *pw, void *node,
21 lwc_string *name, bool *match);
22 static css_error node_has_id(void *pw, void *node,
23 lwc_string *name, bool *match);
24 static css_error node_has_attribute(void *pw, void *node,
25 const css_qname *qname, bool *match);
26 static css_error node_has_attribute_equal(void *pw, void *node,
27 const css_qname *qname, lwc_string *value,
28 bool *match);
29
30
31 /**
32 * Resolve a relative URL to an absolute one by doing nothing. This is
33 * the simplest possible implementation of a URL resolver, needed for
34 * parsing CSS.
35 */
36 css_error svgtiny_resolve_url(void *pw,
37 const char *base, lwc_string *rel, lwc_string **abs)
38 {
39 UNUSED(pw);
40 UNUSED(base);
41
42 /* Copy the relative URL to the absolute one (the return
43 value) */
44 *abs = lwc_string_ref(rel);
45 return CSS_OK;
46 }
47
48 /**
49 * Create a stylesheet with the default set of params.
50 *
51 * \param sheet A stylesheet pointer, passed in by reference, that
52 * we use to store the newly-created stylesheet.
53 * \param inline_style True if this stylesheet represents an inline
54 * style, and false otherwise.
55 *
56 * \return The return value from css_stylesheet_create() is returned.
57 */
58 css_error svgtiny_create_stylesheet(css_stylesheet **sheet,
59 bool inline_style)
60 {
61 css_stylesheet_params params;
62
63 params.params_version = CSS_STYLESHEET_PARAMS_VERSION_1;
64 params.level = CSS_LEVEL_DEFAULT;
65 params.charset = NULL;
66 params.url = "";
67 params.title = NULL;
68 params.allow_quirks = false;
69 params.inline_style = inline_style;
70 params.resolve = svgtiny_resolve_url;
71 params.resolve_pw = NULL;
72 params.import = NULL;
73 params.import_pw = NULL;
74 params.color = NULL;
75 params.color_pw = NULL;
76 params.font = NULL;
77 params.font_pw = NULL;
78
79 return css_stylesheet_create(&params, sheet);
80 }
81
82
83 /**************************/
84 /* libcss select handlers */
85 /**************************/
86 /*
87 * From here on we implement the "select handler "API defined in
88 * libcss's include/libcss/select.h and discussed briefly in its
89 * docs/API document.
90 */
91
92
93 /**
94 * Retrieve the given node's name
95 *
96 * \param pw Pointer to the current SVG parser state
97 * \param node Libdom SVG node
98 * \param qname Address at which to store the node name
99 *
100 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
101 */
102 css_error node_name(void *pw, void *node, css_qname *qname)
103 {
104 dom_string *name;
105 dom_exception err;
106 struct svgtiny_parse_state *state;
107
108 err = dom_node_get_node_name((dom_node *)node, &name);
109 if (err != DOM_NO_ERR) {
110 return CSS_NOMEM;
111 }
112
113 state = (struct svgtiny_parse_state *)pw;
114 qname->ns = lwc_string_ref(state->interned_svg_xmlns);
115
116 err = dom_string_intern(name, &qname->name);
117 if (err != DOM_NO_ERR) {
118 dom_string_unref(name);
119 return CSS_NOMEM;
120 }
121
122 dom_string_unref(name);
123
124 return CSS_OK;
125 }
126
127
128 /**
129 * Retrieve the given node's classes
130 *
131 * \param pw Pointer to the current SVG parser state
132 * \param node Libdom SVG node
133 * \param classes Address at which to store the class name array
134 * \param n_classes Address at which to store the length of the class
135 * name array
136 *
137 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
138 *
139 * \note CSS_NOMEM is not possible in practice as of libdom-0.4.1,
140 * because the underlying libdom function never fails
141 */
142 css_error node_classes(void *pw, void *node,
143 lwc_string ***classes, uint32_t *n_classes)
144 {
145 UNUSED(pw);
146 dom_exception err;
147
148 err = dom_element_get_classes((dom_node *)node, classes, n_classes);
149
150 /* The implementation does not do it, but the documentation
151 for dom_element_get_classes() says that a DOM_NO_MEM_ERR is
152 possible here, so we handle it to be on the safe side. */
153 if (err != DOM_NO_ERR) {
154 return CSS_NOMEM;
155 }
156
157 return CSS_OK;
158 }
159
160
161 /**
162 * Retrieve the given node's id
163 *
164 * \param pw Pointer to the current SVG parser state
165 * \param node Libdom SVG node
166 * \param id Address at which to store the id
167 *
168 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
169 */
170 css_error node_id(void *pw, void *node, lwc_string **id)
171 {
172 dom_string *attr;
173 dom_exception err;
174 struct svgtiny_parse_state *state;
175
176 /* Begin with the assumption that this node has no id */
177 *id = NULL;
178
179 state = (struct svgtiny_parse_state *)pw;
180 err = dom_element_get_attribute((dom_node *)node,
181 state->interned_id, &attr);
182 if (err != DOM_NO_ERR) {
183 return CSS_NOMEM;
184 }
185 else if (attr == NULL) {
186 /* The node has no id attribute and our return value
187 is already set to NULL so we're done */
188 return CSS_OK;
189 }
190
191 /* If we found an id attribute (a dom_string), intern it into
192 an lwc_string that we can return, and then cleanup the
193 dom_string. */
194 err = dom_string_intern(attr, id);
195 if (err != DOM_NO_ERR) {
196 dom_string_unref(attr);
197 return CSS_NOMEM;
198 }
199 dom_string_unref(attr);
200 return CSS_OK;
201 }
202
203
204
205 /**
206 * Find the first parent of the given element having the given name
207 *
208 * \param pw Pointer to the current SVG parser state
209 * \param node Libdom SVG node
210 * \param qname Name of the parent node to search for
211 * \param parent Address at which to store the parent node pointer
212 *
213 * \return Always returns CSS_OK
214 *
215 * \post If a suitable element is found, a pointer to it will be
216 * stored at the address pointed to by \a parent; otherwise,
217 * NULL will be stored at the address pointed to by \a parent
218 */
219 css_error named_parent_node(void *pw, void *node,
220 const css_qname *qname, void **parent)
221 {
222 UNUSED(pw);
223 /* dom_element_named_parent_node() was invented to implement
224 * this select handler so there isn't much for us to do except
225 * call it. It's OK if node isn't an element, libdom checks
226 * for it. */
227 dom_element_named_parent_node((dom_element *)node,
228 qname->name,
229 (struct dom_element **)parent);
230
231 /* Implementation detail: dom_element_named_parent_node()
232 * increments the reference count of the parent element before
233 * returning it to us. According to docs/RefCnt in the libdom
234 * repository, this will prevent the parent element from being
235 * destroyed if it is pruned from the DOM. That sounds good,
236 * since we don't want to be using a pointer to an object that
237 * has been destroyed... but we also have no way of later
238 * decrementing the reference count ourselves, and don't want
239 * to make the returned node eternal. Decrementing the
240 * reference counter now allows it to be destroyed when the
241 * DOM no longer needs it, and so long as no other parts of
242 * libsvgtiny are messing with the DOM during parsing, that
243 * shouldn't (ha ha) cause any problems. */
244 dom_node_unref(*parent);
245
246 return CSS_OK;
247 }
248
249
250 /**
251 * Find the "next-sibling" of the given element having the given name
252 *
253 * This search corresponds to the "+ foo" combinator in CSS and will
254 * find only "foo" element nodes that immediately precede the given
255 * node under the same parent in the DOM. In CSS the tree is viewed
256 * top-down and in libdom it is viewed from the bottom-up; as a result
257 * "next" and "previous" are sometimes backwards. This is case-sensitive.
258 *
259 * \param pw Pointer to the current SVG parser state
260 * \param node Libdom SVG node
261 * \param qname Name of the sibling node to search for
262 * \param sibling Address at which to store the sibling node pointer
263 *
264 * \return Always returns CSS_OK
265 *
266 * \post If a suitable element is found, a pointer to it will be
267 * stored at the address pointed to by \a sibling; otherwise,
268 * NULL will be stored at the address pointed to by \a sibling
269 */
270 css_error named_sibling_node(void *pw, void *node,
271 const css_qname *qname, void **sibling)
272 {
273 UNUSED(pw);
274 dom_node *n = node; /* the current node */
275 dom_node *prev; /* the previous node */
276 dom_exception err;
277 dom_node_type type;
278 dom_string *name;
279
280 *sibling = NULL; /* default to nothing found */
281
282 /* Begin the search; the first iteration we do outside of the
283 * loop. Implementation detil: dom_node_get_previous_sibling()
284 * increments the reference counter on the returned node. A
285 * comment within named_parent_node() explains why we
286 * decrement it ASAP. */
287 err = dom_node_get_previous_sibling(n, &n);
288 if (err != DOM_NO_ERR) {
289 return CSS_OK;
290 }
291
292 while (n != NULL) {
293 /* We're looking for the first ELEMENT sibling */
294 err = dom_node_get_node_type(n, &type);
295 if (err != DOM_NO_ERR) {
296 dom_node_unref(n);
297 return CSS_OK;
298 }
299
300 if (type == DOM_ELEMENT_NODE) {
301 /* We found an element node, does it have the
302 * right name? */
303 err = dom_node_get_node_name(n, &name);
304 if (err != DOM_NO_ERR) {
305 dom_node_unref(n);
306 return CSS_OK;
307 }
308
309 if (dom_string_lwc_isequal(name,
310 qname->name)) {
311 /* The name is right, return it */
312 *sibling = n;
313 }
314
315 /* There's only one next-sibling element node
316 * and we've already found it, so if its name
317 * wasn't right, we return the default value
318 * of NULL below */
319 dom_string_unref(name);
320 dom_node_unref(n);
321 return CSS_OK;
322 }
323
324 /* Not an element node, so we move on the the previous
325 * previous sibling */
326 err = dom_node_get_previous_sibling(n, &prev);
327 if (err != DOM_NO_ERR) {
328 dom_node_unref(n);
329 return CSS_OK;
330 }
331
332 dom_node_unref(n);
333 n = prev;
334 }
335
336 return CSS_OK;
337 }
338
339
340 /**
341 * Find the first "subsequent-sibling" of the given element having the
342 * given name
343 *
344 * This search corresponds to the "~ foo" combinator in CSS and will
345 * find only "foo" element nodes that precede the given node (under
346 * the same parent) in the DOM. In CSS the tree is viewed top-down and
347 * in libdom it is viewed from the bottom-up; as a result "next" and
348 * "previous" are sometimes backwards. This is case-sensitive.
349 *
350 * \param pw Pointer to the current SVG parser state
351 * \param node Libdom SVG node
352 * \param qname Name of the sibling node to search for
353 * \param sibling Address at which to store the sibling node pointer
354 *
355 * \return Always returns CSS_OK
356 *
357 * \post If a suitable element is found, a pointer to it will be
358 * stored at the address pointed to by \a sibling; otherwise,
359 * NULL will be stored at the address pointed to by \a sibling
360 */
361 css_error named_generic_sibling_node(void *pw, void *node,
362 const css_qname *qname, void **sibling)
363 {
364 UNUSED(pw);
365 dom_node *n = node; /* the current node */
366 dom_node *prev; /* the previous node */
367 dom_exception err;
368 dom_node_type type;
369 dom_string *name;
370
371
372 *sibling = NULL; /* default to nothing found */
373
374 /* Begin the search; the first iteration we do outside of the
375 * loop. Implementation detil: dom_node_get_previous_sibling()
376 * increments the reference counter on the returned node. A
377 * comment within named_parent_node() explains why we
378 * decrement it ASAP. */
379 err = dom_node_get_previous_sibling(n, &n);
380 if (err != DOM_NO_ERR) {
381 return CSS_OK;
382 }
383
384 while (n != NULL) {
385 err = dom_node_get_node_type(n, &type);
386 if (err != DOM_NO_ERR) {
387 dom_node_unref(n);
388 return CSS_OK;
389 }
390
391 if (type == DOM_ELEMENT_NODE) {
392 /* We only want ELEMENT nodes */
393 err = dom_node_get_node_name(n, &name);
394 if (err != DOM_NO_ERR) {
395 dom_node_unref(n);
396 return CSS_OK;
397 }
398
399 if (dom_string_lwc_isequal(name,
400 qname->name)) {
401 /* Found one. Save it and stop the search */
402 dom_string_unref(name);
403 dom_node_unref(n);
404 *sibling = n;
405 return CSS_OK;
406 }
407
408 dom_string_unref(name);
409 }
410
411 /* This sibling wasn't an element with the desired
412 name, so move on to the previous sibling */
413 err = dom_node_get_previous_sibling(n, &prev);
414 if (err != DOM_NO_ERR) {
415 dom_node_unref(n);
416 return CSS_OK;
417 }
418
419 dom_node_unref(n);
420 n = prev;
421 }
422
423 return CSS_OK;
424 }
425
426
427 /**
428 * Return a pointer to the given node's parent
429 *
430 * \param pw Pointer to the current SVG parser state
431 * \param node Libdom SVG node
432 * \param parent Address at which to store the node's parent pointer
433 *
434 * \return Always returns CSS_OK
435 */
436 css_error parent_node(void *pw, void *node, void **parent)
437 {
438 UNUSED(pw);
439 /* Libdom basically implements this for us */
440 dom_element_parent_node(node, (struct dom_element **)parent);
441
442 /* See the comment in named_parent_node() for why we decrement
443 * this reference counter here. */
444 dom_node_unref(*parent);
445
446 return CSS_OK;
447 }
448
449
450 /**
451 * Find the "next-sibling" of the given element
452 *
453 * This search corresponds "+ *" in CSS and will find the first
454 * element node that immediately precedes the given node under the
455 * same parent in the DOM. In CSS the tree is viewed top-down and in
456 * libdom it is viewed from the bottom-up; as a result "next" and
457 * "previous" are sometimes backwards.
458 *
459 * \param pw Pointer to the current SVG parser state
460 * \param node Libdom SVG node
461 * \param sibling Address at which to store the sibling node pointer
462 *
463 * \return Always returns CSS_OK
464 *
465 * \post If a suitable element is found, a pointer to it will be
466 * stored at the address pointed to by \a sibling; otherwise,
467 * NULL will be stored at the address pointed to by \a sibling
468 */
469 css_error sibling_node(void *pw, void *node, void **sibling)
470 {
471 UNUSED(pw);
472 dom_node *n = node; /* the current node */
473 dom_node *prev; /* the previous node */
474 dom_exception err;
475 dom_node_type type;
476
477 *sibling = NULL; /* default to nothing found */
478
479 /* Begin the search; the first iteration we do outside of the
480 * loop. Implementation detil: dom_node_get_previous_sibling()
481 * increments the reference counter on the returned node. A
482 * comment within named_parent_node() explains why we
483 * decrement it ASAP. */
484 err = dom_node_get_previous_sibling(n, &n);
485 if (err != DOM_NO_ERR) {
486 return CSS_OK;
487 }
488
489 while (n != NULL) {
490 err = dom_node_get_node_type(n, &type);
491 if (err != DOM_NO_ERR) {
492 dom_node_unref(n);
493 return CSS_OK;
494 }
495
496 if (type == DOM_ELEMENT_NODE) {
497 /* We found a sibling node that is also an
498 element and that's all we wanted. */
499 *sibling = n;
500 dom_node_unref(n);
501 return CSS_OK;
502 }
503
504 /* This sibling node was not an element; move on to
505 the previous sibling */
506 err = dom_node_get_previous_sibling(n, &prev);
507 if (err != DOM_NO_ERR) {
508 dom_node_unref(n);
509 return CSS_OK;
510 }
511
512 dom_node_unref(n);
513 n = prev;
514 }
515
516 return CSS_OK;
517 }
518
519
520 /**
521 * Test the given node for the given name
522 *
523 * This will return true (via the "match" pointer) if the libdom node
524 * has the given name or if that name is the universal selector;
525 * otherwise it returns false. The comparison is case-sensitive. It
526 * corresponds to a rule like "body { ... }" in CSS.
527 *
528 * \param pw Pointer to the current SVG parser state
529 * \param node Libdom SVG node to test
530 * \param qname Name to check for
531 * \param match Pointer to the test result
532 *
533 * \return Always returns CSS_OK
534 */
535 css_error node_has_name(void *pw, void *node,
536 const css_qname *qname, bool *match)
537 {
538 struct svgtiny_parse_state *state;
539 dom_string *name;
540 dom_exception err;
541
542 /* Start by checking to see if qname is the universal selector */
543 state = (struct svgtiny_parse_state *)pw;
544 if (lwc_string_isequal(qname->name,
545 state->interned_universal, match) == lwc_error_ok) {
546 if (*match) {
547 /* It's the universal selector. In NetSurf, all node
548 * names match the universal selector, and nothing in
549 * the libcss documentation suggests another approach,
550 * so we follow NetSurf here. */
551 return CSS_OK;
552 }
553 }
554
555 err = dom_node_get_node_name((dom_node *)node, &name);
556 if (err != DOM_NO_ERR) {
557 return CSS_OK;
558 }
559
560 /* Unlike with HTML, SVG element names are case-sensitive */
561 *match = dom_string_lwc_isequal(name, qname->name);
562 dom_string_unref(name);
563
564 return CSS_OK;
565 }
566
567
568 /**
569 * Test the given node for the given class
570 *
571 * This will return true (via the "match" pointer) if the libdom node
572 * has the given class. The comparison is case-sensitive. It
573 * corresponds to node.class in CSS.
574 *
575 * \param pw Pointer to the current SVG parser state
576 * \param node Libdom SVG node to test
577 * \param name Class name to check for
578 * \param match Pointer to the test result
579 *
580 * \return Always returns CSS_OK
581 */
582 css_error node_has_class(void *pw, void *node,
583 lwc_string *name, bool *match)
584 {
585 UNUSED(pw);
586 /* libdom implements this for us and apparently it cannot fail */
587 dom_element_has_class((dom_node *)node, name, match);
588 return CSS_OK;
589 }
590
591
592 /**
593 * Test the given node for the given id
594 *
595 * This will return true (via the "match" pointer) if the libdom node
596 * has the given id. The comparison is case-sensitive. It corresponds
597 * to node#id in CSS.
598 *
599 * \param pw Pointer to the current SVG parser state
600 * \param node Libdom SVG node to test
601 * \param name Id to check for
602 * \param match Pointer to the test result
603 *
604 * \return Always returns CSS_OK
605 */
606 css_error node_has_id(void *pw, void *node,
607 lwc_string *name, bool *match)
608 {
609 dom_string *attr;
610 dom_exception err;
611 struct svgtiny_parse_state *state;
612
613 attr = NULL; /* a priori the "id" attribute may not exist */
614 *match = false; /* default to no match */
615
616 state = (struct svgtiny_parse_state *)pw;
617 err = dom_element_get_attribute((dom_node *)node,
618 state->interned_id, &attr);
619 if (err != DOM_NO_ERR || attr == NULL) {
620 return CSS_OK;
621 }
622
623 *match = dom_string_lwc_isequal(attr, name);
624 dom_string_unref(attr);
625
626 return CSS_OK;
627 }
628
629
630 /**
631 * Test the given node for the given attribute
632 *
633 * This will return true (via the "match" pointer) if the libdom node
634 * has an attribute with the given name. The comparison is
635 * case-sensitive. It corresponds to node[attr] in CSS.
636 *
637 * \param pw Pointer to the current SVG parser state
638 * \param node Libdom SVG node to test
639 * \param qname Attribute name to check for
640 * \param match Pointer to the test result
641 *
642 * \return Returns CSS_OK if successful and CSS_NOMEM if anything
643 * goes wrong
644 */
645 css_error node_has_attribute(void *pw, void *node,
646 const css_qname *qname, bool *match)
647 {
648 UNUSED(pw);
649 dom_string *name;
650 dom_exception err;
651
652 /* intern the attribute name as a dom_string so we can
653 * delegate to dom_element_has_attribute() */
654 err = dom_string_create_interned(
655 (const uint8_t *) lwc_string_data(qname->name),
656 lwc_string_length(qname->name),
657 &name);
658 if (err != DOM_NO_ERR) {
659 return CSS_NOMEM;
660 }
661
662 err = dom_element_has_attribute((dom_node *)node, name, match);
663 if (err != DOM_NO_ERR) {
664 dom_string_unref(name);
665 return CSS_OK;
666 }
667
668 dom_string_unref(name);
669 return CSS_OK;
670 }
671
672
673 /**
674 * Test the given node for an attribute with a specific value
675 *
676 * This will return true (via the "match" pointer) if the libdom node
677 * has an attribute with the given name and value. The comparison is
678 * case-sensitive. It corresponds to node[attr=value] in CSS.
679 *
680 * \param pw Pointer to the current SVG parser state
681 * \param node Libdom SVG node to test
682 * \param qname Attribute name to check for
683 * \param value Attribute value to check for
684 * \param match Pointer to the test result
685 *
686 * \return Returns CSS_OK if successful and CSS_NOMEM if we cannot
687 * intern the attribute name (which usually indicates memory
688 * exhaustion)
689 */
690 css_error node_has_attribute_equal(void *pw, void *node,
691 const css_qname *qname, lwc_string *value,
692 bool *match)
693 {
694 /* Implementation note: NetSurf always returns "no match" when
695 * the value is empty (length zero). We allow it, because why
696 * not? */
697
698 UNUSED(pw);
699 dom_string *name;
700 dom_string *attr_val;
701 dom_exception err;
702
703 /* Intern the attribute name as a dom_string so we can
704 * use dom_element_get_attribute() */
705 err = dom_string_create_interned(
706 (const uint8_t *) lwc_string_data(qname->name),
707 lwc_string_length(qname->name),
708 &name);
709 if (err != DOM_NO_ERR) {
710 return CSS_NOMEM;
711 }
712
713 err = dom_element_get_attribute((dom_node *)node, name, &attr_val);
714 if ((err != DOM_NO_ERR) || (attr_val == NULL)) {
715 /* There was an error getting the attribute's value or
716 * the attribute doesn't exist. So, no match? */
717 dom_string_unref(name);
718 *match = false;
719 return CSS_OK;
720 }
721
722 /* Otherwise, we have the attribute value from the given node
723 * and all we need to do is compare. */
724 dom_string_unref(name);
725 *match = dom_string_lwc_isequal(attr_val, value);
726 dom_string_unref(attr_val);
727
728 return CSS_OK;
729 }