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