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