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