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