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