]> gitweb.michael.orlitzky.com - libsvgtiny.git/blob - src/svgtiny_css.c
src/svgtiny_css.c: add a missing break statement
[libsvgtiny.git] / src / svgtiny_css.c
1 #include <libcss/libcss.h>
2 #include <strings.h> /* strncasecmp */
3
4 #include "svgtiny.h"
5 #include "svgtiny_internal.h"
6
7 /* select handler callbacks */
8 static css_error node_name(void *pw, void *node, css_qname *qname);
9 static css_error node_classes(void *pw, void *node,
10 lwc_string ***classes, uint32_t *n_classes);
11 static css_error node_id(void *pw, void *node, lwc_string **id);
12 static css_error named_ancestor_node(void *pw, void *node,
13 const css_qname *qname, void **ancestor);
14 static css_error named_parent_node(void *pw, void *node,
15 const css_qname *qname, void **parent);
16 static css_error named_sibling_node(void *pw, void *node,
17 const css_qname *qname, void **sibling);
18 static css_error named_generic_sibling_node(void *pw, void *node,
19 const css_qname *qname, void **sibling);
20 static css_error parent_node(void *pw, void *node, void **parent);
21 static css_error sibling_node(void *pw, void *node, void **sibling);
22 static css_error node_has_name(void *pw, void *node,
23 const css_qname *qname, bool *match);
24 static css_error node_has_class(void *pw, void *node,
25 lwc_string *name, bool *match);
26 static css_error node_has_id(void *pw, void *node,
27 lwc_string *name, bool *match);
28 static css_error node_has_attribute(void *pw, void *node,
29 const css_qname *qname, bool *match);
30 static css_error node_has_attribute_equal(void *pw, void *node,
31 const css_qname *qname, lwc_string *value,
32 bool *match);
33 static css_error node_has_attribute_dashmatch(void *pw, void *node,
34 const css_qname *qname, lwc_string *value,
35 bool *match);
36 static css_error node_has_attribute_includes(void *pw, void *node,
37 const css_qname *qname, lwc_string *word,
38 bool *match);
39 static css_error node_has_attribute_prefix(void *pw, void *node,
40 const css_qname *qname, lwc_string *prefix,
41 bool *match);
42 static css_error node_has_attribute_suffix(void *pw, void *node,
43 const css_qname *qname, lwc_string *suffix,
44 bool *match);
45 static css_error node_has_attribute_substring(void *pw, void *node,
46 const css_qname *qname, lwc_string *substring,
47 bool *match);
48 static css_error node_is_root(void *pw, void *node, bool *match);
49 static css_error node_count_siblings(void *pw, void *node,
50 bool same_name, bool after, int32_t *count);
51 static css_error node_is_empty(void *pw, void *node, bool *is_empty);
52 static css_error node_is_link(void *pw, void *node, bool *is_link);
53 static css_error node_is_visited(void *pw, void *node, bool *is_visited);
54 static css_error node_is_hover(void *pw, void *node, bool *is_hover);
55 static css_error node_is_active(void *pw, void *node, bool *is_active);
56 static css_error node_is_focus(void *pw, void *node, bool *is_focus);
57 static css_error node_is_enabled(void *pw, void *node, bool *is_enabled);
58 static css_error node_is_disabled(void *pw, void *node, bool *is_disabled);
59 static css_error node_is_checked(void *pw, void *node, bool *is_checked);
60 static css_error node_is_target(void *pw, void *node, bool *is_target);
61 static css_error node_is_lang(void *pw, void *node,
62 lwc_string *lang, bool *is_lang);
63 static css_error node_presentational_hint(void *pw, void *node,
64 uint32_t *nhints, css_hint **hints);
65 static css_error ua_default_for_property(void *pw, uint32_t property,
66 css_hint *hint);
67 static css_error set_libcss_node_data(void *pw, void *node,
68 void *libcss_node_data);
69 static css_error get_libcss_node_data(void *pw, void *node,
70 void **libcss_node_data);
71
72 /* select handler vtable */
73 static struct css_select_handler svgtiny_select_handler;
74
75
76 /* Every call to svgtiny_select_style() needs this, so let's only make
77 * one copy. */
78 static const css_media media_all = {
79 .type = CSS_MEDIA_ALL,
80 };
81
82 /**
83 * Convenient wrapper around css_select_style()
84 *
85 * \param state The current state of the libsvgtiny parser
86 * \param node The node that we're getting styles for
87 * \param inline_sheet The inline stylesheet for the given node
88 * \param result Address at which to store the results array
89 */
90 css_error svgtiny_select_style(struct svgtiny_parse_state *state,
91 dom_element *node,
92 const css_stylesheet *inline_sheet,
93 css_select_results **result)
94 {
95 return css_select_style(state->select_ctx,
96 node,
97 &state->unit_ctx,
98 &media_all,
99 inline_sheet,
100 &svgtiny_select_handler,
101 state,
102 result);
103 }
104
105 /**
106 * Resolve a relative URL to an absolute one by doing nothing. This is
107 * the simplest possible implementation of a URL resolver, needed for
108 * parsing CSS.
109 */
110 css_error svgtiny_resolve_url(void *pw,
111 const char *base, lwc_string *rel, lwc_string **abs)
112 {
113 UNUSED(pw);
114 UNUSED(base);
115
116 /* Copy the relative URL to the absolute one (the return
117 value) */
118 *abs = lwc_string_ref(rel);
119 return CSS_OK;
120 }
121
122 /**
123 * Create a stylesheet with the default set of params.
124 *
125 * \param sheet A stylesheet pointer, passed in by reference, that
126 * we use to store the newly-created stylesheet.
127 * \param inline_style True if this stylesheet represents an inline
128 * style, and false otherwise.
129 *
130 * \return The return value from css_stylesheet_create() is returned.
131 */
132 css_error svgtiny_create_stylesheet(css_stylesheet **sheet,
133 bool inline_style)
134 {
135 css_stylesheet_params params;
136
137 params.params_version = CSS_STYLESHEET_PARAMS_VERSION_1;
138 params.level = CSS_LEVEL_DEFAULT;
139 params.charset = NULL;
140 params.url = "";
141 params.title = NULL;
142 params.allow_quirks = false;
143 params.inline_style = inline_style;
144 params.resolve = svgtiny_resolve_url;
145 params.resolve_pw = NULL;
146 params.import = NULL;
147 params.import_pw = NULL;
148 params.color = NULL;
149 params.color_pw = NULL;
150 params.font = NULL;
151 params.font_pw = NULL;
152
153 return css_stylesheet_create(&params, sheet);
154 }
155
156
157 /**************************/
158 /* libcss select handlers */
159 /**************************/
160 /*
161 * From here on we implement the "select handler "API defined in
162 * libcss's include/libcss/select.h and discussed briefly in its
163 * docs/API document.
164 */
165
166
167 /**
168 * Retrieve the given node's name
169 *
170 * \param pw Pointer to the current SVG parser state
171 * \param node Libdom SVG node
172 * \param qname Address at which to store the node name
173 *
174 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
175 */
176 css_error node_name(void *pw, void *node, css_qname *qname)
177 {
178 dom_string *name;
179 dom_exception err;
180 struct svgtiny_parse_state *state;
181
182 err = dom_node_get_node_name((dom_node *)node, &name);
183 if (err != DOM_NO_ERR) {
184 return CSS_NOMEM;
185 }
186
187 state = (struct svgtiny_parse_state *)pw;
188 qname->ns = lwc_string_ref(state->interned_svg_xmlns);
189
190 err = dom_string_intern(name, &qname->name);
191 if (err != DOM_NO_ERR) {
192 dom_string_unref(name);
193 return CSS_NOMEM;
194 }
195
196 dom_string_unref(name);
197
198 return CSS_OK;
199 }
200
201
202 /**
203 * Retrieve the given node's classes
204 *
205 * \param pw Pointer to the current SVG parser state
206 * \param node Libdom SVG node
207 * \param classes Address at which to store the class name array
208 * \param n_classes Address at which to store the length of the class
209 * name array
210 *
211 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
212 *
213 * \note CSS_NOMEM is not possible in practice as of libdom-0.4.1,
214 * because the underlying libdom function never fails
215 */
216 css_error node_classes(void *pw, void *node,
217 lwc_string ***classes, uint32_t *n_classes)
218 {
219 UNUSED(pw);
220 dom_exception err;
221
222 err = dom_element_get_classes((dom_node *)node, classes, n_classes);
223
224 /* The implementation does not do it, but the documentation
225 for dom_element_get_classes() says that a DOM_NO_MEM_ERR is
226 possible here, so we handle it to be on the safe side. */
227 if (err != DOM_NO_ERR) {
228 return CSS_NOMEM;
229 }
230
231 return CSS_OK;
232 }
233
234
235 /**
236 * Retrieve the given node's id
237 *
238 * \param pw Pointer to the current SVG parser state
239 * \param node Libdom SVG node
240 * \param id Address at which to store the id
241 *
242 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
243 */
244 css_error node_id(void *pw, void *node, lwc_string **id)
245 {
246 dom_string *attr;
247 dom_exception err;
248 struct svgtiny_parse_state *state;
249
250 /* Begin with the assumption that this node has no id */
251 *id = NULL;
252
253 state = (struct svgtiny_parse_state *)pw;
254 err = dom_element_get_attribute((dom_node *)node,
255 state->interned_id, &attr);
256 if (err != DOM_NO_ERR) {
257 return CSS_NOMEM;
258 }
259 else if (attr == NULL) {
260 /* The node has no id attribute and our return value
261 is already set to NULL so we're done */
262 return CSS_OK;
263 }
264
265 /* If we found an id attribute (a dom_string), intern it into
266 an lwc_string that we can return, and then cleanup the
267 dom_string. */
268 err = dom_string_intern(attr, id);
269 if (err != DOM_NO_ERR) {
270 dom_string_unref(attr);
271 return CSS_NOMEM;
272 }
273 dom_string_unref(attr);
274 return CSS_OK;
275 }
276
277
278 /**
279 * Find the first ancestor of the given element having the given name
280 *
281 * This function thinly wraps dom_element_named_ancestor_node(), which
282 * performs exactly the search we want.
283 *
284 * \param pw Pointer to the current SVG parser state
285 * \param node Libdom SVG node whose ancestor we want
286 * \param qname Name of the ancestor node to search for
287 * \param ancestor Address at which to store the ancestor node pointer
288 *
289 * \return Always returns CSS_OK
290 *
291 * \post If a suitable element is found, a pointer to it will be
292 * stored at the address pointed to by \a ancestor; otherwise,
293 * NULL will be stored at the address pointed to by \a ancestor
294 */
295 css_error named_ancestor_node(void *pw, void *node,
296 const css_qname *qname, void **ancestor)
297 {
298 UNUSED(pw);
299
300 /* It's OK if node isn't an element, libdom checks for it. */
301 dom_element_named_ancestor_node((dom_element *)node,
302 qname->name,
303 (struct dom_element **)ancestor);
304
305 /* A comment in named_parent_node() explains why we unref
306 * this here. */
307 dom_node_unref(*ancestor);
308
309 return CSS_OK;
310 }
311
312
313 /**
314 * Find the first parent of the given element having the given name
315 *
316 * \param pw Pointer to the current SVG parser state
317 * \param node Libdom SVG node
318 * \param qname Name of the parent node to search for
319 * \param parent Address at which to store the parent node pointer
320 *
321 * \return Always returns CSS_OK
322 *
323 * \post If a suitable element is found, a pointer to it will be
324 * stored at the address pointed to by \a parent; otherwise,
325 * NULL will be stored at the address pointed to by \a parent
326 */
327 css_error named_parent_node(void *pw, void *node,
328 const css_qname *qname, void **parent)
329 {
330 UNUSED(pw);
331 /* dom_element_named_parent_node() was invented to implement
332 * this select handler so there isn't much for us to do except
333 * call it. It's OK if node isn't an element, libdom checks
334 * for it. */
335 dom_element_named_parent_node((dom_element *)node,
336 qname->name,
337 (struct dom_element **)parent);
338
339 /* Implementation detail: dom_element_named_parent_node()
340 * increments the reference count of the parent element before
341 * returning it to us. According to docs/RefCnt in the libdom
342 * repository, this will prevent the parent element from being
343 * destroyed if it is pruned from the DOM. That sounds good,
344 * since we don't want to be using a pointer to an object that
345 * has been destroyed... but we also have no way of later
346 * decrementing the reference count ourselves, and don't want
347 * to make the returned node eternal. Decrementing the
348 * reference counter now allows it to be destroyed when the
349 * DOM no longer needs it, and so long as no other parts of
350 * libsvgtiny are messing with the DOM during parsing, that
351 * shouldn't (ha ha) cause any problems. */
352 dom_node_unref(*parent);
353
354 return CSS_OK;
355 }
356
357
358 /**
359 * Find the "next-sibling" of the given element having the given name
360 *
361 * This search corresponds to the "+ foo" combinator in CSS and will
362 * find only "foo" element nodes that immediately precede the given
363 * node under the same parent in the DOM. In CSS the tree is viewed
364 * top-down and in libdom it is viewed from the bottom-up; as a result
365 * "next" and "previous" are sometimes backwards. This is case-sensitive.
366 *
367 * \param pw Pointer to the current SVG parser state
368 * \param node Libdom SVG node
369 * \param qname Name of the sibling node to search for
370 * \param sibling Address at which to store the sibling node pointer
371 *
372 * \return Always returns CSS_OK
373 *
374 * \post If a suitable element is found, a pointer to it will be
375 * stored at the address pointed to by \a sibling; otherwise,
376 * NULL will be stored at the address pointed to by \a sibling
377 */
378 css_error named_sibling_node(void *pw, void *node,
379 const css_qname *qname, void **sibling)
380 {
381 UNUSED(pw);
382 dom_node *n = node; /* the current node */
383 dom_node *prev; /* the previous node */
384 dom_exception err;
385 dom_node_type type;
386 dom_string *name;
387
388 *sibling = NULL; /* default to nothing found */
389
390 /* Begin the search; the first iteration we do outside of the
391 * loop. Implementation detil: dom_node_get_previous_sibling()
392 * increments the reference counter on the returned node. A
393 * comment within named_parent_node() explains why we
394 * decrement it ASAP. */
395 err = dom_node_get_previous_sibling(n, &n);
396 if (err != DOM_NO_ERR) {
397 return CSS_OK;
398 }
399
400 while (n != NULL) {
401 /* We're looking for the first ELEMENT sibling */
402 err = dom_node_get_node_type(n, &type);
403 if (err != DOM_NO_ERR) {
404 dom_node_unref(n);
405 return CSS_OK;
406 }
407
408 if (type == DOM_ELEMENT_NODE) {
409 /* We found an element node, does it have the
410 * right name? */
411 err = dom_node_get_node_name(n, &name);
412 if (err != DOM_NO_ERR) {
413 dom_node_unref(n);
414 return CSS_OK;
415 }
416
417 if (dom_string_lwc_isequal(name,
418 qname->name)) {
419 /* The name is right, return it */
420 *sibling = n;
421 }
422
423 /* There's only one next-sibling element node
424 * and we've already found it, so if its name
425 * wasn't right, we return the default value
426 * of NULL below */
427 dom_string_unref(name);
428 dom_node_unref(n);
429 return CSS_OK;
430 }
431
432 /* Not an element node, so we move on the the previous
433 * previous sibling */
434 err = dom_node_get_previous_sibling(n, &prev);
435 if (err != DOM_NO_ERR) {
436 dom_node_unref(n);
437 return CSS_OK;
438 }
439
440 dom_node_unref(n);
441 n = prev;
442 }
443
444 return CSS_OK;
445 }
446
447
448 /**
449 * Find the first "subsequent-sibling" of the given element having the
450 * given name
451 *
452 * This search corresponds to the "~ foo" combinator in CSS and will
453 * find only "foo" element nodes that precede the given node (under
454 * the same parent) in the DOM. In CSS the tree is viewed top-down and
455 * in libdom it is viewed from the bottom-up; as a result "next" and
456 * "previous" are sometimes backwards. This is case-sensitive.
457 *
458 * \param pw Pointer to the current SVG parser state
459 * \param node Libdom SVG node
460 * \param qname Name of the sibling node to search for
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 named_generic_sibling_node(void *pw, void *node,
470 const css_qname *qname, void **sibling)
471 {
472 UNUSED(pw);
473 dom_node *n = node; /* the current node */
474 dom_node *prev; /* the previous node */
475 dom_exception err;
476 dom_node_type type;
477 dom_string *name;
478
479
480 *sibling = NULL; /* default to nothing found */
481
482 /* Begin the search; the first iteration we do outside of the
483 * loop. Implementation detil: dom_node_get_previous_sibling()
484 * increments the reference counter on the returned node. A
485 * comment within named_parent_node() explains why we
486 * decrement it ASAP. */
487 err = dom_node_get_previous_sibling(n, &n);
488 if (err != DOM_NO_ERR) {
489 return CSS_OK;
490 }
491
492 while (n != NULL) {
493 err = dom_node_get_node_type(n, &type);
494 if (err != DOM_NO_ERR) {
495 dom_node_unref(n);
496 return CSS_OK;
497 }
498
499 if (type == DOM_ELEMENT_NODE) {
500 /* We only want ELEMENT nodes */
501 err = dom_node_get_node_name(n, &name);
502 if (err != DOM_NO_ERR) {
503 dom_node_unref(n);
504 return CSS_OK;
505 }
506
507 if (dom_string_lwc_isequal(name,
508 qname->name)) {
509 /* Found one. Save it and stop the search */
510 dom_string_unref(name);
511 dom_node_unref(n);
512 *sibling = n;
513 return CSS_OK;
514 }
515
516 dom_string_unref(name);
517 }
518
519 /* This sibling wasn't an element with the desired
520 name, so move on to the previous sibling */
521 err = dom_node_get_previous_sibling(n, &prev);
522 if (err != DOM_NO_ERR) {
523 dom_node_unref(n);
524 return CSS_OK;
525 }
526
527 dom_node_unref(n);
528 n = prev;
529 }
530
531 return CSS_OK;
532 }
533
534
535 /**
536 * Return a pointer to the given node's parent
537 *
538 * \param pw Pointer to the current SVG parser state
539 * \param node Libdom SVG node
540 * \param parent Address at which to store the node's parent pointer
541 *
542 * \return Always returns CSS_OK
543 */
544 css_error parent_node(void *pw, void *node, void **parent)
545 {
546 UNUSED(pw);
547 /* Libdom basically implements this for us */
548 dom_element_parent_node(node, (struct dom_element **)parent);
549
550 /* See the comment in named_parent_node() for why we decrement
551 * this reference counter here. */
552 dom_node_unref(*parent);
553
554 return CSS_OK;
555 }
556
557
558 /**
559 * Find the "next-sibling" of the given element
560 *
561 * This search corresponds "+ *" in CSS and will find the first
562 * element node that immediately precedes the given node under the
563 * same parent in the DOM. In CSS the tree is viewed top-down and in
564 * libdom it is viewed from the bottom-up; as a result "next" and
565 * "previous" are sometimes backwards.
566 *
567 * \param pw Pointer to the current SVG parser state
568 * \param node Libdom SVG node
569 * \param sibling Address at which to store the sibling node pointer
570 *
571 * \return Always returns CSS_OK
572 *
573 * \post If a suitable element is found, a pointer to it will be
574 * stored at the address pointed to by \a sibling; otherwise,
575 * NULL will be stored at the address pointed to by \a sibling
576 */
577 css_error sibling_node(void *pw, void *node, void **sibling)
578 {
579 UNUSED(pw);
580 dom_node *n = node; /* the current node */
581 dom_node *prev; /* the previous node */
582 dom_exception err;
583 dom_node_type type;
584
585 *sibling = NULL; /* default to nothing found */
586
587 /* Begin the search; the first iteration we do outside of the
588 * loop. Implementation detil: dom_node_get_previous_sibling()
589 * increments the reference counter on the returned node. A
590 * comment within named_parent_node() explains why we
591 * decrement it ASAP. */
592 err = dom_node_get_previous_sibling(n, &n);
593 if (err != DOM_NO_ERR) {
594 return CSS_OK;
595 }
596
597 while (n != NULL) {
598 err = dom_node_get_node_type(n, &type);
599 if (err != DOM_NO_ERR) {
600 dom_node_unref(n);
601 return CSS_OK;
602 }
603
604 if (type == DOM_ELEMENT_NODE) {
605 /* We found a sibling node that is also an
606 element and that's all we wanted. */
607 *sibling = n;
608 dom_node_unref(n);
609 return CSS_OK;
610 }
611
612 /* This sibling node was not an element; move on to
613 the previous sibling */
614 err = dom_node_get_previous_sibling(n, &prev);
615 if (err != DOM_NO_ERR) {
616 dom_node_unref(n);
617 return CSS_OK;
618 }
619
620 dom_node_unref(n);
621 n = prev;
622 }
623
624 return CSS_OK;
625 }
626
627
628 /**
629 * Test the given node for the given name
630 *
631 * This will return true (via the "match" pointer) if the libdom node
632 * has the given name or if that name is the universal selector;
633 * otherwise it returns false. The comparison is case-sensitive. It
634 * corresponds to a rule like "body { ... }" in CSS.
635 *
636 * \param pw Pointer to the current SVG parser state
637 * \param node Libdom SVG node to test
638 * \param qname Name to check for
639 * \param match Pointer to the test result
640 *
641 * \return Always returns CSS_OK
642 */
643 css_error node_has_name(void *pw, void *node,
644 const css_qname *qname, bool *match)
645 {
646 struct svgtiny_parse_state *state;
647 dom_string *name;
648 dom_exception err;
649
650 /* Start by checking to see if qname is the universal selector */
651 state = (struct svgtiny_parse_state *)pw;
652 *match = dom_string_lwc_isequal(state->interned_universal, qname->name);
653 if (*match) {
654 /* It's the universal selector. In NetSurf, all node
655 * names match the universal selector, and nothing in
656 * the libcss documentation suggests another approach,
657 * so we follow NetSurf here. */
658 return CSS_OK;
659 }
660
661 err = dom_node_get_node_name((dom_node *)node, &name);
662 if (err != DOM_NO_ERR) {
663 return CSS_OK;
664 }
665
666 /* Unlike with HTML, SVG element names are case-sensitive */
667 *match = dom_string_lwc_isequal(name, qname->name);
668 dom_string_unref(name);
669
670 return CSS_OK;
671 }
672
673
674 /**
675 * Test the given node for the given class
676 *
677 * This will return true (via the "match" pointer) if the libdom node
678 * has the given class. The comparison is case-sensitive. It
679 * corresponds to node.class in CSS.
680 *
681 * \param pw Pointer to the current SVG parser state
682 * \param node Libdom SVG node to test
683 * \param name Class name to check for
684 * \param match Pointer to the test result
685 *
686 * \return Always returns CSS_OK
687 */
688 css_error node_has_class(void *pw, void *node,
689 lwc_string *name, bool *match)
690 {
691 UNUSED(pw);
692 /* libdom implements this for us and apparently it cannot fail */
693 dom_element_has_class((dom_node *)node, name, match);
694 return CSS_OK;
695 }
696
697
698 /**
699 * Test the given node for the given id
700 *
701 * This will return true (via the "match" pointer) if the libdom node
702 * has the given id. The comparison is case-sensitive. It corresponds
703 * to node#id in CSS.
704 *
705 * \param pw Pointer to the current SVG parser state
706 * \param node Libdom SVG node to test
707 * \param name Id to check for
708 * \param match Pointer to the test result
709 *
710 * \return Always returns CSS_OK
711 */
712 css_error node_has_id(void *pw, void *node,
713 lwc_string *name, bool *match)
714 {
715 dom_string *attr;
716 dom_exception err;
717 struct svgtiny_parse_state *state;
718
719 attr = NULL; /* a priori the "id" attribute may not exist */
720 *match = false; /* default to no match */
721
722 state = (struct svgtiny_parse_state *)pw;
723 err = dom_element_get_attribute((dom_node *)node,
724 state->interned_id, &attr);
725 if (err != DOM_NO_ERR || attr == NULL) {
726 return CSS_OK;
727 }
728
729 *match = dom_string_lwc_isequal(attr, name);
730 dom_string_unref(attr);
731
732 return CSS_OK;
733 }
734
735
736 /**
737 * Test the given node for the given attribute
738 *
739 * This will return true (via the "match" pointer) if the libdom node
740 * has an attribute with the given name. The comparison is
741 * case-sensitive. It corresponds to node[attr] in CSS.
742 *
743 * \param pw Pointer to the current SVG parser state
744 * \param node Libdom SVG node to test
745 * \param qname Attribute name to check for
746 * \param match Pointer to the test result
747 *
748 * \return Returns CSS_OK if successful and CSS_NOMEM if anything
749 * goes wrong
750 */
751 css_error node_has_attribute(void *pw, void *node,
752 const css_qname *qname, bool *match)
753 {
754 UNUSED(pw);
755 dom_string *name;
756 dom_exception err;
757
758 /* intern the attribute name as a dom_string so we can
759 * delegate to dom_element_has_attribute() */
760 err = dom_string_create_interned(
761 (const uint8_t *) lwc_string_data(qname->name),
762 lwc_string_length(qname->name),
763 &name);
764 if (err != DOM_NO_ERR) {
765 return CSS_NOMEM;
766 }
767
768 err = dom_element_has_attribute((dom_node *)node, name, match);
769 if (err != DOM_NO_ERR) {
770 dom_string_unref(name);
771 return CSS_OK;
772 }
773
774 dom_string_unref(name);
775 return CSS_OK;
776 }
777
778
779 /**
780 * Test the given node for an attribute with a specific value
781 *
782 * This will return true (via the "match" pointer) if the libdom node
783 * has an attribute with the given name and value. The comparison is
784 * case-sensitive. It corresponds to node[attr=value] in CSS.
785 *
786 * \param pw Pointer to the current SVG parser state
787 * \param node Libdom SVG node to test
788 * \param qname Attribute name to check for
789 * \param value Attribute value to check for
790 * \param match Pointer to the test result
791 *
792 * \return Returns CSS_OK if successful and CSS_NOMEM if we cannot
793 * intern the attribute name (which usually indicates memory
794 * exhaustion)
795 */
796 css_error node_has_attribute_equal(void *pw, void *node,
797 const css_qname *qname, lwc_string *value,
798 bool *match)
799 {
800 /* Implementation note: NetSurf always returns "no match" when
801 * the value is empty (length zero). We allow it, because why
802 * not? */
803
804 UNUSED(pw);
805 dom_string *name;
806 dom_string *attr_val;
807 dom_exception err;
808
809 /* Intern the attribute name as a dom_string so we can
810 * use dom_element_get_attribute() */
811 err = dom_string_create_interned(
812 (const uint8_t *) lwc_string_data(qname->name),
813 lwc_string_length(qname->name),
814 &name);
815 if (err != DOM_NO_ERR) {
816 return CSS_NOMEM;
817 }
818
819 err = dom_element_get_attribute((dom_node *)node, name, &attr_val);
820 if ((err != DOM_NO_ERR) || (attr_val == NULL)) {
821 /* There was an error getting the attribute's value or
822 * the attribute doesn't exist. So, no match? */
823 dom_string_unref(name);
824 *match = false;
825 return CSS_OK;
826 }
827
828 /* Otherwise, we have the attribute value from the given node
829 * and all we need to do is compare. */
830 dom_string_unref(name);
831 *match = dom_string_lwc_isequal(attr_val, value);
832 dom_string_unref(attr_val);
833
834 return CSS_OK;
835 }
836
837
838 /**
839 * Test the given node for an attribute with a specific value,
840 * possibly followed by a single hyphen
841 *
842 * This will return true (via the "match" pointer) if the libdom node
843 * has an attribute with the given name and value or with the given
844 * name and a value that is followed by exactly one hyphen. The
845 * comparison is case-sensitive. This corresponds to [attr|=value]
846 * in CSS.
847 *
848 * \param pw Pointer to the current SVG parser state
849 * \param node Libdom SVG node to test
850 * \param qname Attribute name to check for
851 * \param value Attribute value to check for
852 * \param match Pointer to the test result
853 *
854 * \return Returns CSS_OK if successful and CSS_NOMEM if we cannot
855 * intern the attribute name (which usually indicates memory
856 * exhaustion)
857 */
858 css_error node_has_attribute_dashmatch(void *pw, void *node,
859 const css_qname *qname, lwc_string *value,
860 bool *match)
861 {
862 /* Implementation note: NetSurf always returns "no match" when
863 * the value is empty (length zero). We allow it, because why
864 * not? */
865
866 UNUSED(pw);
867 dom_string *name;
868 dom_string *attr_val;
869 dom_exception err;
870
871 const char *vdata; /* to hold the data underlying "value" */
872 size_t vdata_len;
873 const char *avdata; /* to hold the found attribute value data */
874 size_t avdata_len;
875
876 /* Intern the attribute name as a dom_string so we can
877 * use dom_element_get_attribute() */
878 err = dom_string_create_interned(
879 (const uint8_t *) lwc_string_data(qname->name),
880 lwc_string_length(qname->name),
881 &name);
882 if (err != DOM_NO_ERR) {
883 return CSS_NOMEM;
884 }
885
886 err = dom_element_get_attribute((dom_node *)node, name, &attr_val);
887 if ((err != DOM_NO_ERR) || (attr_val == NULL)) {
888 /* There was an error getting the attribute's value or
889 * the attribute doesn't exist. So, no match? */
890 dom_string_unref(name);
891 *match = false;
892 return CSS_OK;
893 }
894
895 /* Otherwise, we have the attribute value from the given node
896 * and all we need to do is compare. */
897 dom_string_unref(name);
898 *match = dom_string_lwc_isequal(attr_val, value);
899 if (*match) {
900 /* Exact match, we're done */
901 dom_string_unref(attr_val);
902 return CSS_OK;
903 }
904
905 /* No exact match, try it with a hyphen on the end */
906 vdata = lwc_string_data(value); /* needle */
907 vdata_len = lwc_string_length(value);
908 avdata = dom_string_data(attr_val); /* haystack */
909 avdata_len = dom_string_byte_length(attr_val);
910 dom_string_unref(attr_val);
911
912 if (avdata_len > vdata_len && avdata[vdata_len] == '-') {
913 if (strncasecmp(avdata, vdata, vdata_len) == 0) {
914 /* If there's a hyphen in the right position,
915 * it suffices to compare the strings only up
916 * to the hyphen */
917 *match = true;
918 }
919 }
920
921 return CSS_OK;
922 }
923
924
925 /**
926 * Test the given node for an attribute whose value is a
927 * space-separated list of words, one of which is the given word
928 *
929 * This will return true (via the "match" pointer) if the libdom node
930 * has an attribute with the given name and whose value when
931 * considered as a space-separated list of words contains the given
932 * word. The comparison is case-sensitive. This corresponds to
933 * [attr~=value] in CSS.
934 *
935 * \param pw Pointer to the current SVG parser state
936 * \param node Libdom SVG node to test
937 * \param qname Attribute name to check for
938 * \param word Value word to check for
939 * \param match Pointer to the test result
940 *
941 * \return Returns CSS_OK if successful and CSS_NOMEM if we cannot
942 * intern the attribute name (which usually indicates memory
943 * exhaustion)
944 */
945 css_error node_has_attribute_includes(void *pw, void *node,
946 const css_qname *qname, lwc_string *word,
947 bool *match)
948 {
949 UNUSED(pw);
950
951 dom_string *name;
952 dom_string *attr_val;
953 dom_exception err;
954 size_t wordlen; /* length of "word" */
955
956 /* pointers used to parse a space-separated list of words */
957 const char *p;
958 const char *start;
959 const char *end;
960
961 *match = false; /* default to no match */
962
963 wordlen = lwc_string_length(word);
964 if (wordlen == 0) {
965 /* In this case, the spec says that "if 'val' is the
966 * empty string, it will never represent anything." */
967 return CSS_OK;
968 }
969
970 /* Intern the attribute name as a dom_string so we can
971 * use dom_element_get_attribute() */
972 err = dom_string_create_interned(
973 (const uint8_t *) lwc_string_data(qname->name),
974 lwc_string_length(qname->name),
975 &name);
976 if (err != DOM_NO_ERR) {
977 return CSS_NOMEM;
978 }
979
980 err = dom_element_get_attribute((dom_node *)node, name, &attr_val);
981 if ((err != DOM_NO_ERR) || (attr_val == NULL)) {
982 /* There was an error getting the attribute's value or
983 * the attribute doesn't exist. So, no match? */
984 dom_string_unref(name);
985 return CSS_OK;
986 }
987
988 /* Parse the list comparing each word against "word" */
989 start = dom_string_data(attr_val);
990 end = start + dom_string_byte_length(attr_val);
991 dom_string_unref(attr_val);
992
993 for (p = start; p <= end; p++) {
994 /* Move forward until we find the end of the first word */
995 if (*p == ' ' || *p == '\0') {
996 /* If the length of that word is the length of the
997 * word we're looking for, do the comparison. */
998 if ((size_t) (p - start) == wordlen &&
999 strncasecmp(start,
1000 lwc_string_data(word),
1001 wordlen) == 0) {
1002 *match = true;
1003 break;
1004 }
1005 /* No match? Set "start" to the beginning of
1006 * the next word and loop. */
1007 start = p + 1;
1008 }
1009 }
1010
1011 return CSS_OK;
1012 }
1013
1014
1015 /**
1016 * Test the given node for an attribute whose value begins with the
1017 * given prefix
1018 *
1019 * This will return true (via the "match" pointer) if the libdom node
1020 * has an attribute with the given name and whose value begins with
1021 * the given prefix string. The comparison is case-sensitive. This
1022 * corresponds to [attr^=value] in CSS.
1023 *
1024 * \param pw Pointer to the current SVG parser state
1025 * \param node Libdom SVG node to test
1026 * \param qname Attribute name to check for
1027 * \param prefix Value prefix to check for
1028 * \param match Pointer to the test result
1029 *
1030 * \return Returns CSS_OK if successful and CSS_NOMEM if we cannot
1031 * intern the attribute name (which usually indicates memory
1032 * exhaustion)
1033 */
1034 css_error node_has_attribute_prefix(void *pw, void *node,
1035 const css_qname *qname, lwc_string *prefix,
1036 bool *match)
1037 {
1038 UNUSED(pw);
1039 dom_string *name;
1040 dom_string *attr_val;
1041 dom_exception err;
1042 const char *avdata; /* attribute value data */
1043 size_t avdata_len; /* length of that attribute value data */
1044 size_t prefixlen; /* length of "prefix" */
1045
1046 prefixlen = lwc_string_length(prefix);
1047 if (prefixlen == 0) {
1048 /* In this case, the spec says that "if 'val' is the
1049 * empty string, it will never represent anything." */
1050 return CSS_OK;
1051 }
1052
1053 /* Intern the attribute name as a dom_string so we can
1054 * use dom_element_get_attribute() */
1055 err = dom_string_create_interned(
1056 (const uint8_t *) lwc_string_data(qname->name),
1057 lwc_string_length(qname->name),
1058 &name);
1059 if (err != DOM_NO_ERR) {
1060 return CSS_NOMEM;
1061 }
1062
1063 err = dom_element_get_attribute((dom_node *)node, name, &attr_val);
1064 if ((err != DOM_NO_ERR) || (attr_val == NULL)) {
1065 /* There was an error getting the attribute's value or
1066 * the attribute doesn't exist. So, no match? */
1067 dom_string_unref(name);
1068 *match = false;
1069 return CSS_OK;
1070 }
1071
1072 /* Otherwise, we have the attribute value from the given node,
1073 * and the first thing we want to do is check to see if the
1074 * whole thing matches the prefix. */
1075 dom_string_unref(name);
1076 *match = dom_string_lwc_isequal(attr_val, prefix);
1077
1078 /* If not, check to see if an, uh, prefix matches the
1079 * prefix */
1080 if (*match == false) {
1081 avdata = dom_string_data(attr_val);
1082 avdata_len = dom_string_byte_length(attr_val);
1083 if ((avdata_len >= prefixlen) &&
1084 (strncasecmp(avdata,
1085 lwc_string_data(prefix),
1086 prefixlen) == 0)) {
1087 /* Use strncasecmp to compare only the first
1088 * "n" characters, where "n" is the length of
1089 * the prefix. */
1090 *match = true;
1091 }
1092 }
1093
1094 dom_string_unref(attr_val);
1095
1096 return CSS_OK;
1097 }
1098
1099
1100 /**
1101 * Test the given node for an attribute whose value end with the
1102 * given suffix
1103 *
1104 * This will return true (via the "match" pointer) if the libdom node
1105 * has an attribute with the given name and whose value ends with
1106 * the given suffix string. The comparison is case-sensitive. This
1107 * corresponds to [attr$=value] in CSS.
1108 *
1109 * \param pw Pointer to the current SVG parser state
1110 * \param node Libdom SVG node to test
1111 * \param qname Attribute name to check for
1112 * \param suffix Value suffix to check for
1113 * \param match Pointer to the test result
1114 *
1115 * \return Returns CSS_OK if successful and CSS_NOMEM if we cannot
1116 * intern the attribute name (which usually indicates memory
1117 * exhaustion)
1118 */
1119 css_error node_has_attribute_suffix(void *pw, void *node,
1120 const css_qname *qname, lwc_string *suffix,
1121 bool *match)
1122 {
1123 UNUSED(pw);
1124 dom_string *name;
1125 dom_string *attr_val;
1126 dom_exception err;
1127 const char *avdata; /* attribute value data */
1128 size_t avdata_len; /* length of that attribute value data */
1129 size_t suffixlen; /* length of "suffix" */
1130
1131 /* convenience pointer we'll use when matching the suffix */
1132 const char *suffix_start;
1133
1134 suffixlen = lwc_string_length(suffix);
1135 if (suffixlen == 0) {
1136 /* In this case, the spec says that "if 'val' is the
1137 * empty string, it will never represent anything." */
1138 return CSS_OK;
1139 }
1140
1141 /* Intern the attribute name as a dom_string so we can
1142 * use dom_element_get_attribute() */
1143 err = dom_string_create_interned(
1144 (const uint8_t *) lwc_string_data(qname->name),
1145 lwc_string_length(qname->name),
1146 &name);
1147 if (err != DOM_NO_ERR) {
1148 return CSS_NOMEM;
1149 }
1150
1151 err = dom_element_get_attribute((dom_node *)node, name, &attr_val);
1152 if ((err != DOM_NO_ERR) || (attr_val == NULL)) {
1153 /* There was an error getting the attribute's value or
1154 * the attribute doesn't exist. So, no match? */
1155 dom_string_unref(name);
1156 *match = false;
1157 return CSS_OK;
1158 }
1159
1160 /* Otherwise, we have the attribute value from the given node,
1161 * and the first thing we want to do is check to see if the
1162 * whole thing matches the suffix. */
1163 dom_string_unref(name);
1164 *match = dom_string_lwc_isequal(attr_val, suffix);
1165
1166 /* If not, check to see if an, uh, suffix matches the
1167 * suffix */
1168 if (*match == false) {
1169 avdata = dom_string_data(attr_val);
1170 avdata_len = dom_string_byte_length(attr_val);
1171
1172 suffix_start = (char *)(avdata + avdata_len - suffixlen);
1173
1174 if ((avdata_len >= suffixlen) &&
1175 (strncasecmp(suffix_start,
1176 lwc_string_data(suffix),
1177 suffixlen) == 0)) {
1178 /* Use strncasecmp to compare only the last
1179 * "n" characters, where "n" is the length of
1180 * the suffix. */
1181 *match = true;
1182 }
1183 }
1184
1185 dom_string_unref(attr_val);
1186
1187 return CSS_OK;
1188 }
1189
1190
1191 /**
1192 * Implement node_has_attribute_substring() with optional case-
1193 * insensitivity. This corresponds to [attr*=value i] in CSS and is
1194 * not supported by libcss yet, but it allows us to factor out some
1195 * common code.
1196 */
1197 static css_error _node_has_attribute_substring(void *pw, void *node,
1198 const css_qname *qname, lwc_string *substring,
1199 bool *match, bool insensitive)
1200 {
1201 UNUSED(pw);
1202 dom_string *name;
1203 dom_string *attr_val;
1204 dom_exception err;
1205 size_t attr_len; /* length of attr_val */
1206 size_t substrlen; /* length of "substring" */
1207
1208 /* Convenience pointers we use when comparing substrings */
1209 const char *p;
1210 const char *p_max;
1211
1212 substrlen = lwc_string_length(substring);
1213 if (substrlen == 0) {
1214 /* In this case, the spec says that "if 'val' is the
1215 * empty string, it will never represent anything." */
1216 return CSS_OK;
1217 }
1218
1219 /* Intern the attribute name as a dom_string so we can
1220 * use dom_element_get_attribute() */
1221 err = dom_string_create_interned(
1222 (const uint8_t *) lwc_string_data(qname->name),
1223 lwc_string_length(qname->name),
1224 &name);
1225 if (err != DOM_NO_ERR) {
1226 return CSS_NOMEM;
1227 }
1228
1229 err = dom_element_get_attribute((dom_node *)node, name, &attr_val);
1230 if ((err != DOM_NO_ERR) || (attr_val == NULL)) {
1231 /* There was an error getting the attribute's value or
1232 * the attribute doesn't exist. So, no match? */
1233 dom_string_unref(name);
1234 *match = false;
1235 return CSS_OK;
1236 }
1237
1238 /* Otherwise, we have the attribute value from the given node,
1239 * and the first thing we want to do is check to see if the
1240 * whole thing matches the substring. */
1241 dom_string_unref(name);
1242
1243 if (insensitive) {
1244 *match = dom_string_caseless_lwc_isequal(attr_val, substring);
1245 }
1246 else {
1247 *match = dom_string_lwc_isequal(attr_val, substring);
1248 }
1249
1250 /* If not, check to see if an, uh, substring matches the
1251 * substring */
1252 if (*match == false) {
1253 p = dom_string_data(attr_val);
1254
1255 /* Check every long-enough suffix for a prefix match */
1256 attr_len = dom_string_byte_length(attr_val);
1257 if (attr_len >= substrlen) {
1258 p_max = p + attr_len - substrlen;
1259 while (p <= p_max) {
1260 if (strncasecmp(p,
1261 lwc_string_data(substring),
1262 substrlen) == 0) {
1263 *match = true;
1264 break;
1265 }
1266 p++;
1267 }
1268 }
1269 }
1270
1271 dom_string_unref(attr_val);
1272
1273 return CSS_OK;
1274 }
1275
1276 /**
1277 * Test the given node for an attribute whose value contains the
1278 * given substring
1279 *
1280 * This will return true (via the "match" pointer) if the libdom node
1281 * has an attribute with the given name and whose value contains the
1282 * given substring. The comparison is case-sensitive. This corresponds
1283 * to [attr*=value] in CSS.
1284 *
1285 * \param pw Pointer to the current SVG parser state
1286 * \param node Libdom SVG node to test
1287 * \param qname Attribute name to check for
1288 * \param substring Value substring to check for
1289 * \param match Pointer to the test result
1290 *
1291 * \return Returns CSS_OK if successful and CSS_NOMEM if we cannot
1292 * intern the attribute name (which usually indicates memory
1293 * exhaustion)
1294 */
1295 css_error node_has_attribute_substring(void *pw, void *node,
1296 const css_qname *qname, lwc_string *substring,
1297 bool *match)
1298 {
1299 return _node_has_attribute_substring(pw, node, qname, substring,
1300 match, false);
1301 }
1302
1303
1304 /**
1305 * Test whether or not the given node is the document's root element
1306 * This corresponds to the CSS :root pseudo-selector.
1307 *
1308 * \param pw Pointer to the current SVG parser state
1309 * \param node Libdom SVG node to test
1310 * \param match Pointer to the test result
1311 *
1312 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
1313 */
1314 css_error node_is_root(void *pw, void *node, bool *match)
1315 {
1316 UNUSED(pw);
1317 dom_node *parent;
1318 dom_node_type type;
1319 dom_exception err;
1320
1321 err = dom_node_get_parent_node((dom_node *)node, &parent);
1322 if (err != DOM_NO_ERR) {
1323 return CSS_NOMEM;
1324 }
1325
1326 /* It's the root element if it doesn't have a parent element */
1327 if (parent != NULL) {
1328 err = dom_node_get_node_type(parent, &type);
1329 dom_node_unref(parent);
1330 if (err != DOM_NO_ERR) {
1331 return CSS_NOMEM;
1332 }
1333 if (type != DOM_DOCUMENT_NODE) {
1334 /* DOM_DOCUMENT_NODE is the only allowable
1335 * type of parent node for the root element */
1336 *match = false;
1337 return CSS_OK;
1338 }
1339 }
1340
1341 *match = true;
1342 return CSS_OK;
1343 }
1344
1345
1346 /**
1347 * Used internally in node_count_siblings() to "count" the given
1348 * sibling node. It factors out the node type and name checks.
1349 */
1350 static int node_count_siblings_check(dom_node *dnode,
1351 bool check_name,
1352 dom_string *name)
1353 {
1354 int ret;
1355 dom_node_type type;
1356 dom_exception exc;
1357 dom_string *dnode_name;
1358
1359 /* We flip this to 1 if/when we count this node */
1360 ret = 0;
1361
1362 if (dnode == NULL) {
1363 return ret;
1364 }
1365
1366 exc = dom_node_get_node_type(dnode, &type);
1367 if ((exc != DOM_NO_ERR) || (type != DOM_ELEMENT_NODE)) {
1368 /* We only count element siblings */
1369 return ret;
1370 }
1371
1372 /* ... with the right name */
1373 if (check_name) {
1374 dnode_name = NULL;
1375 exc = dom_node_get_node_name(dnode, &dnode_name);
1376
1377 if ((exc == DOM_NO_ERR) && (dnode_name != NULL)) {
1378 if (dom_string_isequal(name,
1379 dnode_name)) {
1380 ret = 1;
1381 }
1382 dom_string_unref(dnode_name);
1383 }
1384 }
1385 else {
1386 ret = 1;
1387 }
1388
1389 return ret;
1390 }
1391
1392 /**
1393 * Count the given node's sibling elements
1394 *
1395 * This counts the given node's sibling elements in one direction,
1396 * either forwards or backwards, in the DOM. Keep in mind that the
1397 * libdom tree is upside-down compared to the CSS one; so "next" and
1398 * "previous" are actually reversed; the default is to count preceding
1399 * libdom siblings which correspond to subsequent CSS siblings.
1400 *
1401 * This operation is central to the CSS :first-child, :nth-child, and
1402 * :last-child (et cetera) pseudo-selectors.
1403 *
1404 * If same_name is true, then only nodes having the same
1405 * (case-sensitive) name as the given node are counted.
1406 *
1407 * \param pw Pointer to the current SVG parser state
1408 * \param node Libdom SVG node whose siblings we're counting
1409 * \param same_name Whether or not to count only siblings having
1410 * the same name as the given node
1411 * \param after Count subsequent siblings rather than precedent
1412 * ones (the default)
1413 * \param count Pointer to the return value, the number of sibling
1414 * elements
1415 *
1416 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
1417 */
1418 css_error node_count_siblings(void *pw, void *node,
1419 bool same_name, bool after, int32_t *count)
1420 {
1421 UNUSED(pw);
1422 dom_exception exc;
1423 dom_node *dnode; /* node, but with the right type */
1424 dom_string *dnode_name;
1425 dom_node *next; /* "next" sibling (depends on direction) */
1426
1427 /* Pointer to the "next sibling" function */
1428 dom_exception (*next_func)(dom_node *, dom_node **);
1429
1430 *count = 0;
1431
1432 dnode_name = NULL;
1433 dnode = (dom_node *)node;
1434 if (same_name) {
1435 exc = dom_node_get_node_name(dnode, &dnode_name);
1436 if ((exc != DOM_NO_ERR) || (dnode_name == NULL)) {
1437 return CSS_NOMEM;
1438 }
1439 }
1440
1441 /* Increment the reference counter for dnode for as long as
1442 * we retain a reference to it. */
1443 dnode = dom_node_ref(dnode);
1444
1445 next_func = dom_node_get_previous_sibling;
1446 if (after) {
1447 next_func = dom_node_get_next_sibling;
1448 }
1449
1450 do {
1451 exc = next_func(dnode, &next);
1452 if (exc != DOM_NO_ERR) {
1453 break;
1454 }
1455
1456 /* If next_func worked, we're about to swap "next"
1457 * with "dnode" meaning that we will no longer retain
1458 * a reference to the current dnode. */
1459 dom_node_unref(dnode);
1460 dnode = next;
1461
1462 *count += node_count_siblings_check(dnode,
1463 same_name,
1464 dnode_name);
1465 } while (dnode != NULL);
1466
1467 if (dnode_name != NULL) {
1468 dom_string_unref(dnode_name);
1469 }
1470
1471 return CSS_OK;
1472 }
1473
1474
1475 /**
1476 * Determine whether or not the given element is empty
1477 *
1478 * An element is "nonempty" if it has a child that is either an
1479 * element node or a text node.
1480 *
1481 * \param pw Pointer to the current SVG parser state
1482 * \param node Libdom SVG node to check for emptiness
1483 * \param is_empty Pointer to the return value
1484 *
1485 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
1486 */
1487 css_error node_is_empty(void *pw, void *node, bool *is_empty)
1488 {
1489 UNUSED(pw);
1490 dom_node *child; /* current child node pointer */
1491 dom_node *next; /* next child node pointer */
1492 dom_node_type type; /* what type of node is "child" */
1493 dom_exception err;
1494
1495 /* Assume that it's empty by default */
1496 *is_empty = true;
1497
1498 /* Get the given node's first child. Implementation detail:
1499 * this increments the reference counter on the child node. */
1500 err = dom_node_get_first_child((dom_node *)node, &child);
1501 if (err != DOM_NO_ERR) {
1502 return CSS_NOMEM;
1503 }
1504
1505 /* And now loop through all children looking for a
1506 * text/element node. If we find one, the original
1507 * node is "nonempty" */
1508 while (child != NULL) {
1509 err = dom_node_get_node_type(child, &type);
1510 if (err != DOM_NO_ERR) {
1511 dom_node_unref(child);
1512 return CSS_NOMEM;
1513 }
1514
1515 if (type == DOM_ELEMENT_NODE || type == DOM_TEXT_NODE) {
1516 *is_empty = false;
1517 dom_node_unref(child);
1518 return CSS_OK;
1519 }
1520
1521 err = dom_node_get_next_sibling(child, &next);
1522 if (err != DOM_NO_ERR) {
1523 dom_node_unref(child);
1524 return CSS_NOMEM;
1525 }
1526
1527 /* If we're moving to the next node, we can release
1528 * the reference to the current one */
1529 dom_node_unref(child);
1530 child = next;
1531 }
1532
1533 return CSS_OK;
1534 }
1535
1536
1537 /**
1538 * Determine whether or not the given node is a link
1539 *
1540 * A node is a link if it is an element node whose name is "a" and if
1541 * it has an "href" attribute (case-sensitive). This selector
1542 * corresponds to node:link pseudo-class in CSS.
1543 *
1544 * This pseudo-class is a bit awkward because the two standards (HTML5
1545 * and CSS) disagree on what it means, and because libsvgtiny does not
1546 * have enough information to determine if a link has been "visited"
1547 * yet -- that's a UI property. CSS says that :link is for unvisited
1548 * links, which we can't determine. HTML5 says that each link must
1549 * be either a :link or :visited. Since we can't decide either way,
1550 * It seems less wrong to declare that all links are unvisited; i.e.
1551 * that they match :link.
1552 *
1553 * \param pw Pointer to the current SVG parser state
1554 * \param node Libdom SVG node to check
1555 * \param is_link Pointer to the boolean return value
1556 *
1557 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
1558 */
1559 css_error node_is_link(void *pw, void *node, bool *is_link)
1560 {
1561 dom_exception exc;
1562 dom_node *dnode; /* node, but with the right type */
1563 dom_string *dnode_name;
1564 bool has_href;
1565 struct svgtiny_parse_state* state;
1566
1567 dnode = (dom_node *)node;
1568 dnode_name = NULL;
1569 has_href = false; /* assume no href attribute */
1570 *is_link = false; /* assume that it's not a link */
1571
1572 exc = dom_node_get_node_name(dnode, &dnode_name);
1573 if ((exc != DOM_NO_ERR) || (dnode_name == NULL)) {
1574 return CSS_NOMEM;
1575 }
1576
1577 state = (struct svgtiny_parse_state *)pw;
1578 if (dom_string_isequal(dnode_name, state->interned_a)) {
1579 exc = dom_element_has_attribute(node,
1580 state->interned_href,
1581 &has_href);
1582 if (exc == DOM_NO_ERR && has_href) {
1583 *is_link = true;
1584 }
1585 }
1586
1587 dom_string_unref(dnode_name);
1588 return CSS_OK;
1589 }
1590
1591 /**
1592 * Check if the given node is a link that has been visited already
1593 *
1594 * This check always fails because the SVG DOM does not have the
1595 * necessary information (it's a UI property).
1596 *
1597 * \param pw Pointer to the current SVG parser state; unused
1598 * \param node Libdom SVG node to check; unused
1599 * \param is_visited Pointer to the boolean return value
1600 *
1601 * \return Always returns CSS_OK
1602 */
1603 css_error node_is_visited(void *pw, void *node, bool *is_visited)
1604 {
1605 UNUSED(pw);
1606 UNUSED(node);
1607 *is_visited = false;
1608 return CSS_OK;
1609 }
1610
1611
1612 /**
1613 * Check if the given node is being "hovered" over
1614 *
1615 * This check always fails because the SVG DOM does not have the
1616 * necessary information (it's a UI property).
1617 *
1618 * \param pw Pointer to the current SVG parser state; unused
1619 * \param node Libdom SVG node to check; unused
1620 * \param is_hover Pointer to the boolean return value
1621 *
1622 * \return Always returns CSS_OK
1623 */
1624 css_error node_is_hover(void *pw, void *node, bool *is_hover)
1625 {
1626 UNUSED(pw);
1627 UNUSED(node);
1628 *is_hover = false;
1629 return CSS_OK;
1630 }
1631
1632
1633 /**
1634 * Check if the given node is "active"
1635 *
1636 * This check always fails because the SVG DOM does not have the
1637 * necessary information (it's a UI property).
1638 *
1639 * \param pw Pointer to the current SVG parser state; unused
1640 * \param node Libdom SVG node to check; unused
1641 * \param is_active Pointer to the boolean return value
1642 *
1643 * \return Always returns CSS_OK
1644 */
1645 css_error node_is_active(void *pw, void *node, bool *is_active)
1646 {
1647 UNUSED(pw);
1648 UNUSED(node);
1649 *is_active = false;
1650 return CSS_OK;
1651 }
1652
1653
1654 /**
1655 * Check if the given node has the focus
1656 *
1657 * This check always fails because the SVG DOM does not have the
1658 * necessary information (it's a UI property).
1659 *
1660 * \param pw Pointer to the current SVG parser state; unused
1661 * \param node Libdom SVG node to check; unused
1662 * \param is_focus Pointer to the boolean return value
1663 *
1664 * \return Always returns CSS_OK
1665 */
1666 css_error node_is_focus(void *pw, void *node, bool *is_focus)
1667 {
1668 UNUSED(pw);
1669 UNUSED(node);
1670 *is_focus = false;
1671 return CSS_OK;
1672 }
1673
1674
1675 /**
1676 * Check if the given node is enabled
1677 *
1678 * This check always fails because the SVG DOM does not have the
1679 * necessary information (it's a UI property).
1680 *
1681 * \param pw Pointer to the current SVG parser state; unused
1682 * \param node Libdom SVG node to check; unused
1683 * \param is_enabled Pointer to the boolean return value
1684 *
1685 * \return Always returns CSS_OK
1686 */
1687 css_error node_is_enabled(void *pw, void *node, bool *is_enabled)
1688 {
1689 UNUSED(pw);
1690 UNUSED(node);
1691 *is_enabled = false;
1692 return CSS_OK;
1693 }
1694
1695
1696 /**
1697 * Check if the given node is disabled
1698 *
1699 * This check always fails because the SVG DOM does not have the
1700 * necessary information (it's a UI property). Beware, until they are
1701 * implemented, this is NOT the logical negation of node_is_enabled!
1702 *
1703 * \param pw Pointer to the current SVG parser state; unused
1704 * \param node Libdom SVG node to check; unused
1705 * \param is_disabled Pointer to the boolean return value
1706 *
1707 * \return Always returns CSS_OK
1708 */
1709 css_error node_is_disabled(void *pw, void *node, bool *is_disabled)
1710 {
1711 UNUSED(pw);
1712 UNUSED(node);
1713 *is_disabled = false;
1714 return CSS_OK;
1715 }
1716
1717
1718 /**
1719 * Test whether or not the given node is "checked"
1720 *
1721 * This test always fails because the SVG DOM does not have the
1722 * necessary information (it's a UI property).
1723 *
1724 * \param pw Pointer to the current SVG parser state; unused
1725 * \param node Libdom SVG node to check; unused
1726 * \param is_checked Pointer to the boolean return value
1727 *
1728 * \return Always returns CSS_OK
1729 */
1730 css_error node_is_checked(void *pw, void *node, bool *is_checked)
1731 {
1732 UNUSED(pw);
1733 UNUSED(node);
1734 *is_checked = false;
1735 return CSS_OK;
1736 }
1737
1738
1739 /**
1740 * Check if the given node is the "target" of the document URL
1741 *
1742 * This test always fails because the SVG DOM does not have the
1743 * necessary information (it's a UI property).
1744 *
1745 * \param pw Pointer to the current SVG parser state; unused
1746 * \param node Libdom SVG node to check; unused
1747 * \param is_target Pointer to the boolean return value
1748 *
1749 * \return Always returns CSS_OK
1750 */
1751 css_error node_is_target(void *pw, void *node, bool *is_target)
1752 {
1753 UNUSED(pw);
1754 UNUSED(node);
1755 *is_target = false;
1756 return CSS_OK;
1757 }
1758
1759
1760 /**
1761 * Check if the given node is the given language
1762 *
1763 * This test is corresponds to the CSS :lang() selector and is not
1764 * fully implemented yet: it looks only for "lang" attributes on the
1765 * given element and its parents, and performs a simple substring
1766 * check. This results in a partial implementation of CSS Level 3 for
1767 * SVG 2.0. In particular, it ignores all "xml:lang" attributes in
1768 * favor of the "lang" attribute that is defined only in SVG 2.0.
1769 *
1770 * \param pw Pointer to the current SVG parser state; unused
1771 * \param node Libdom SVG node to check
1772 * \param lang The language to match
1773 * \param is_lang Pointer to the boolean return value
1774 *
1775 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
1776 */
1777 static css_error node_is_lang(void *pw, void *node,
1778 lwc_string *lang, bool *is_lang)
1779 {
1780 UNUSED(pw);
1781 /* SVG2 elements support both "lang" and "xml:lang"
1782 * attributes; earlier versions have only the XML
1783 * attribute. It would not be too hard to add support for
1784 * xml:lang" here. The main difficulty standing in the way of
1785 * a full Level 4 implementation is the complexity of the
1786 * :lang() selector:
1787 *
1788 * https://www.w3.org/TR/selectors-4/#the-lang-pseudo
1789 *
1790 */
1791
1792 css_error c_err;
1793 dom_exception d_err;
1794 dom_node *n; /* current node */
1795 dom_node *p; /* parent node */
1796 bool match; /* retval from node_has_attribute_substring() */
1797
1798 /* Define the attribute name "lang" that we're looking for.
1799 * We only use a css_qname here because that's what the
1800 * node_has_attribute_substring() takes; the namespace
1801 * portion of it is irrelevant. */
1802 css_qname attr;
1803 attr.ns = NULL;
1804
1805 if (lwc_intern_string("lang", 4, &attr.name) != lwc_error_ok) {
1806 return CSS_NOMEM;
1807 }
1808
1809 *is_lang = false; /* default to no match */
1810 n = (dom_node *)node;
1811
1812 /* Loop through all parents of the given node looking for a
1813 * substring match */
1814 while (n != NULL) {
1815 c_err = _node_has_attribute_substring(pw, (void *)n, &attr,
1816 lang, &match, true);
1817 if (c_err != CSS_OK) {
1818 lwc_string_destroy(attr.name);
1819 return c_err;
1820 }
1821 if (match) {
1822 /* matched this element; we're done */
1823 lwc_string_destroy(attr.name);
1824 *is_lang = true;
1825 return CSS_OK;
1826 }
1827
1828 /* no match on this element, try its parent */
1829 d_err = dom_node_get_parent_node(n, &p);
1830 if (d_err != DOM_NO_ERR) {
1831 lwc_string_destroy(attr.name);
1832 return CSS_NOMEM;
1833 }
1834 n = p;
1835 }
1836
1837 /* If we never find a match we may wind up here */
1838 lwc_string_destroy(attr.name);
1839 return CSS_OK;
1840 }
1841
1842
1843 /**
1844 * Return presentational hints for the given node
1845 *
1846 * Unless an SVG is being rendered from within an HTML document,
1847 * there are no presentational hints. We always return an empty
1848 * array (no hints).
1849 *
1850 * \param pw Pointer to the current SVG parser state; unused
1851 * \param node Libdom SVG node whose hints we want; unused
1852 * \param nhints How many hints are returned (return by reference)
1853 * \param hints Array of css_hint structures (return by reference)
1854 *
1855 * \return Always returns CSS_OK
1856 */
1857 css_error node_presentational_hint(void *pw, void *node,
1858 uint32_t *nhints, css_hint **hints)
1859 {
1860 UNUSED(pw);
1861 UNUSED(node);
1862 *nhints = 0;
1863 *hints = NULL;
1864 return CSS_OK;
1865 }
1866
1867
1868 /**
1869 * User-agent defaults for CSS properties
1870 *
1871 * Ideally we would provide _no_ defaults here, because we don't yet
1872 * support any CSS properties that can use them. However, we run into
1873 * libcss parent/child style composition issues unless these defaults
1874 * are provided. And it's harmless to provide them, so let's do it.
1875 *
1876 * \param pw Pointer to the current SVG parser state; unused
1877 * \param property LibCSS property identifier; unused
1878 * \param hint Pointer to hint object (a return value); unused
1879 *
1880 * \return Always returns CSS_INVALID
1881 */
1882 css_error ua_default_for_property(void *pw, uint32_t property,
1883 css_hint *hint)
1884 {
1885 UNUSED(pw);
1886 switch (property) {
1887 case CSS_PROP_COLOR:
1888 hint->data.color = 0xff000000;
1889 hint->status = CSS_COLOR_COLOR;
1890 break;
1891 case CSS_PROP_FONT_FAMILY:
1892 hint->data.strings = NULL;
1893 hint->status = CSS_FONT_FAMILY_SANS_SERIF;
1894 break;
1895 case CSS_PROP_QUOTES:
1896 hint->data.strings = NULL;
1897 hint->status = CSS_QUOTES_NONE;
1898 break;
1899 case CSS_PROP_VOICE_FAMILY:
1900 hint->data.strings = NULL;
1901 hint->status = 0;
1902 break;
1903 default:
1904 return CSS_INVALID;
1905 }
1906
1907 return CSS_OK;
1908 }
1909
1910
1911 /**
1912 * A "user handler" function that we pass to dom_node_set_user_data()
1913 * in set_libcss_node_data(). The set_libcss_node_data() documentation
1914 * says that if the node (on which data is set) is is deleted or
1915 * cloned, or if its ancestors are modified, then we must call
1916 * css_libcss_node_data_handler() on the user data. This function
1917 * basically just checks to see which DOM event has happened and
1918 * calls css_libcss_node_data_handler() when any of those criteria
1919 * are met.
1920 */
1921 static void svgtiny_dom_user_data_handler(dom_node_operation operation,
1922 dom_string *key, void *data, struct dom_node *src,
1923 struct dom_node *dst)
1924 {
1925 /* We only care about the userdata that is identified by our
1926 * userdata key. Unfortunately I see no obvious way to obtain
1927 * the copy of the userdata key that is already interned in
1928 * svgtiny_strings.h; so we duplicate it here (ugh). */
1929 dom_string *str;
1930 dom_string_create((const uint8_t *)"_libcss_user_data", 17, &str);
1931 if (dom_string_isequal(str,key) == false || data == NULL) {
1932 /* Wrong key, or no data */
1933 return;
1934 }
1935
1936 /* Check the DOM operation, and make the corresponding call to
1937 * css_libcss_node_data_handler(). No error handling is done.
1938 */
1939 switch (operation) {
1940 case DOM_NODE_CLONED:
1941 css_libcss_node_data_handler(&svgtiny_select_handler,
1942 CSS_NODE_CLONED,
1943 NULL, src, dst, data);
1944 break;
1945 case DOM_NODE_RENAMED:
1946 css_libcss_node_data_handler(&svgtiny_select_handler,
1947 CSS_NODE_MODIFIED,
1948 NULL, src, NULL, data);
1949 break;
1950 case DOM_NODE_IMPORTED:
1951 case DOM_NODE_ADOPTED:
1952 case DOM_NODE_DELETED:
1953 css_libcss_node_data_handler(&svgtiny_select_handler,
1954 CSS_NODE_DELETED,
1955 NULL, src, NULL, data);
1956 break;
1957 default:
1958 /* Our list of cases should have been exhaustive */
1959 assert(false);
1960 }
1961 }
1962
1963 /**
1964 * Store libcss data on a node
1965 *
1966 * This is part of the libcss select handler API that we need to
1967 * implement. It is essentially a thin dom_node_set_user_data()
1968 * wrapper.
1969 *
1970 * \param pw Pointer to the current SVG parser state
1971 * \param node Libdom SVG node on which to store the data
1972 * \param libcss_node_data Pointer to the data to store
1973 *
1974 * \return Always returns CSS_OK
1975 */
1976 css_error set_libcss_node_data(void *pw, void *node,
1977 void *libcss_node_data)
1978 {
1979 struct svgtiny_parse_state *state;
1980 void *old_data;
1981
1982 /* A unique "userdata key" (a string) is used to identify this
1983 * data. */
1984 state = (struct svgtiny_parse_state *)pw;
1985 dom_node_set_user_data((dom_node *)node,
1986 state->interned_userdata_key,
1987 libcss_node_data,
1988 svgtiny_dom_user_data_handler,
1989 &old_data);
1990
1991 /* dom_node_set_user_data() always returns DOM_NO_ERR */
1992 return CSS_OK;
1993 }
1994
1995
1996 /**
1997 * Retrieve libcss data from a node
1998 *
1999 * This is part of the libcss select handler API that we need to
2000 * implement. It is essentially a thin dom_node_get_user_data()
2001 * wrapper.
2002 *
2003 * \param pw Pointer to the current SVG parser state
2004 * \param node Libdom SVG node from which to get the data
2005 * \param libcss_node_data Address at which to store a pointer to the data
2006 *
2007 * \return Always returns CSS_OK
2008 */
2009 css_error get_libcss_node_data(void *pw, void *node,
2010 void **libcss_node_data)
2011 {
2012 struct svgtiny_parse_state *state;
2013
2014 /* A unique "userdata key" (a string) is used to identify this
2015 * data. */
2016 state = (struct svgtiny_parse_state *)pw;
2017 dom_node_get_user_data((dom_node *)node,
2018 state->interned_userdata_key,
2019 libcss_node_data);
2020
2021 /* dom_node_get_user_data() always returns DOM_NO_ERR */
2022 return CSS_OK;
2023 }
2024
2025
2026 /**
2027 * The vtable of select handler callbacks passed by libsvgtiny to
2028 * css_select_style().
2029 */
2030 static css_select_handler svgtiny_select_handler = {
2031 CSS_SELECT_HANDLER_VERSION_1,
2032 node_name,
2033 node_classes,
2034 node_id,
2035 named_ancestor_node,
2036 named_parent_node,
2037 named_sibling_node,
2038 named_generic_sibling_node,
2039 parent_node,
2040 sibling_node,
2041 node_has_name,
2042 node_has_class,
2043 node_has_id,
2044 node_has_attribute,
2045 node_has_attribute_equal,
2046 node_has_attribute_dashmatch,
2047 node_has_attribute_includes,
2048 node_has_attribute_prefix,
2049 node_has_attribute_suffix,
2050 node_has_attribute_substring,
2051 node_is_root,
2052 node_count_siblings,
2053 node_is_empty,
2054 node_is_link,
2055 node_is_visited,
2056 node_is_hover,
2057 node_is_active,
2058 node_is_focus,
2059 node_is_enabled,
2060 node_is_disabled,
2061 node_is_checked,
2062 node_is_target,
2063 node_is_lang,
2064 node_presentational_hint,
2065 ua_default_for_property,
2066 set_libcss_node_data,
2067 get_libcss_node_data,
2068 };