]> gitweb.michael.orlitzky.com - libsvgtiny.git/blob - src/svgtiny_css.c
src/svgtiny_css.c: implement node_is_link() select handler
[libsvgtiny.git] / src / svgtiny_css.c
1 #include <libcss/libcss.h>
2 #include <strings.h> /* strncasecmp */
3
4 #include "svgtiny.h"
5 #include "svgtiny_internal.h"
6
7 static css_error node_name(void *pw, void *node, css_qname *qname);
8 static css_error node_classes(void *pw, void *node,
9 lwc_string ***classes, uint32_t *n_classes);
10 static css_error node_id(void *pw, void *node, lwc_string **id);
11 static css_error named_parent_node(void *pw, void *node,
12 const css_qname *qname, void **parent);
13 static css_error named_sibling_node(void *pw, void *node,
14 const css_qname *qname, void **sibling);
15 static css_error named_generic_sibling_node(void *pw, void *node,
16 const css_qname *qname, void **sibling);
17 static css_error parent_node(void *pw, void *node, void **parent);
18 static css_error sibling_node(void *pw, void *node, void **sibling);
19 static css_error node_has_name(void *pw, void *node,
20 const css_qname *qname, bool *match);
21 static css_error node_has_class(void *pw, void *node,
22 lwc_string *name, bool *match);
23 static css_error node_has_id(void *pw, void *node,
24 lwc_string *name, bool *match);
25 static css_error node_has_attribute(void *pw, void *node,
26 const css_qname *qname, bool *match);
27 static css_error node_has_attribute_equal(void *pw, void *node,
28 const css_qname *qname, lwc_string *value,
29 bool *match);
30 static css_error node_has_attribute_dashmatch(void *pw, void *node,
31 const css_qname *qname, lwc_string *value,
32 bool *match);
33 static css_error node_has_attribute_includes(void *pw, void *node,
34 const css_qname *qname, lwc_string *word,
35 bool *match);
36 static css_error node_has_attribute_prefix(void *pw, void *node,
37 const css_qname *qname, lwc_string *prefix,
38 bool *match);
39 static css_error node_has_attribute_suffix(void *pw, void *node,
40 const css_qname *qname, lwc_string *suffix,
41 bool *match);
42 static css_error node_has_attribute_substring(void *pw, void *node,
43 const css_qname *qname, lwc_string *substring,
44 bool *match);
45 static css_error node_is_root(void *pw, void *node, bool *match);
46 static css_error node_count_siblings(void *pw, void *node,
47 bool same_name, bool after, int32_t *count);
48 static css_error node_is_empty(void *pw, void *node, bool *is_empty);
49 static css_error node_is_link(void *pw, void *node, bool *is_link);
50
51
52 /**
53 * Resolve a relative URL to an absolute one by doing nothing. This is
54 * the simplest possible implementation of a URL resolver, needed for
55 * parsing CSS.
56 */
57 css_error svgtiny_resolve_url(void *pw,
58 const char *base, lwc_string *rel, lwc_string **abs)
59 {
60 UNUSED(pw);
61 UNUSED(base);
62
63 /* Copy the relative URL to the absolute one (the return
64 value) */
65 *abs = lwc_string_ref(rel);
66 return CSS_OK;
67 }
68
69 /**
70 * Create a stylesheet with the default set of params.
71 *
72 * \param sheet A stylesheet pointer, passed in by reference, that
73 * we use to store the newly-created stylesheet.
74 * \param inline_style True if this stylesheet represents an inline
75 * style, and false otherwise.
76 *
77 * \return The return value from css_stylesheet_create() is returned.
78 */
79 css_error svgtiny_create_stylesheet(css_stylesheet **sheet,
80 bool inline_style)
81 {
82 css_stylesheet_params params;
83
84 params.params_version = CSS_STYLESHEET_PARAMS_VERSION_1;
85 params.level = CSS_LEVEL_DEFAULT;
86 params.charset = NULL;
87 params.url = "";
88 params.title = NULL;
89 params.allow_quirks = false;
90 params.inline_style = inline_style;
91 params.resolve = svgtiny_resolve_url;
92 params.resolve_pw = NULL;
93 params.import = NULL;
94 params.import_pw = NULL;
95 params.color = NULL;
96 params.color_pw = NULL;
97 params.font = NULL;
98 params.font_pw = NULL;
99
100 return css_stylesheet_create(&params, sheet);
101 }
102
103
104 /**************************/
105 /* libcss select handlers */
106 /**************************/
107 /*
108 * From here on we implement the "select handler "API defined in
109 * libcss's include/libcss/select.h and discussed briefly in its
110 * docs/API document.
111 */
112
113
114 /**
115 * Retrieve the given node's name
116 *
117 * \param pw Pointer to the current SVG parser state
118 * \param node Libdom SVG node
119 * \param qname Address at which to store the node name
120 *
121 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
122 */
123 css_error node_name(void *pw, void *node, css_qname *qname)
124 {
125 dom_string *name;
126 dom_exception err;
127 struct svgtiny_parse_state *state;
128
129 err = dom_node_get_node_name((dom_node *)node, &name);
130 if (err != DOM_NO_ERR) {
131 return CSS_NOMEM;
132 }
133
134 state = (struct svgtiny_parse_state *)pw;
135 qname->ns = lwc_string_ref(state->interned_svg_xmlns);
136
137 err = dom_string_intern(name, &qname->name);
138 if (err != DOM_NO_ERR) {
139 dom_string_unref(name);
140 return CSS_NOMEM;
141 }
142
143 dom_string_unref(name);
144
145 return CSS_OK;
146 }
147
148
149 /**
150 * Retrieve the given node's classes
151 *
152 * \param pw Pointer to the current SVG parser state
153 * \param node Libdom SVG node
154 * \param classes Address at which to store the class name array
155 * \param n_classes Address at which to store the length of the class
156 * name array
157 *
158 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
159 *
160 * \note CSS_NOMEM is not possible in practice as of libdom-0.4.1,
161 * because the underlying libdom function never fails
162 */
163 css_error node_classes(void *pw, void *node,
164 lwc_string ***classes, uint32_t *n_classes)
165 {
166 UNUSED(pw);
167 dom_exception err;
168
169 err = dom_element_get_classes((dom_node *)node, classes, n_classes);
170
171 /* The implementation does not do it, but the documentation
172 for dom_element_get_classes() says that a DOM_NO_MEM_ERR is
173 possible here, so we handle it to be on the safe side. */
174 if (err != DOM_NO_ERR) {
175 return CSS_NOMEM;
176 }
177
178 return CSS_OK;
179 }
180
181
182 /**
183 * Retrieve the given node's id
184 *
185 * \param pw Pointer to the current SVG parser state
186 * \param node Libdom SVG node
187 * \param id Address at which to store the id
188 *
189 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
190 */
191 css_error node_id(void *pw, void *node, lwc_string **id)
192 {
193 dom_string *attr;
194 dom_exception err;
195 struct svgtiny_parse_state *state;
196
197 /* Begin with the assumption that this node has no id */
198 *id = NULL;
199
200 state = (struct svgtiny_parse_state *)pw;
201 err = dom_element_get_attribute((dom_node *)node,
202 state->interned_id, &attr);
203 if (err != DOM_NO_ERR) {
204 return CSS_NOMEM;
205 }
206 else if (attr == NULL) {
207 /* The node has no id attribute and our return value
208 is already set to NULL so we're done */
209 return CSS_OK;
210 }
211
212 /* If we found an id attribute (a dom_string), intern it into
213 an lwc_string that we can return, and then cleanup the
214 dom_string. */
215 err = dom_string_intern(attr, id);
216 if (err != DOM_NO_ERR) {
217 dom_string_unref(attr);
218 return CSS_NOMEM;
219 }
220 dom_string_unref(attr);
221 return CSS_OK;
222 }
223
224
225
226 /**
227 * Find the first parent of the given element having the given name
228 *
229 * \param pw Pointer to the current SVG parser state
230 * \param node Libdom SVG node
231 * \param qname Name of the parent node to search for
232 * \param parent Address at which to store the parent node pointer
233 *
234 * \return Always returns CSS_OK
235 *
236 * \post If a suitable element is found, a pointer to it will be
237 * stored at the address pointed to by \a parent; otherwise,
238 * NULL will be stored at the address pointed to by \a parent
239 */
240 css_error named_parent_node(void *pw, void *node,
241 const css_qname *qname, void **parent)
242 {
243 UNUSED(pw);
244 /* dom_element_named_parent_node() was invented to implement
245 * this select handler so there isn't much for us to do except
246 * call it. It's OK if node isn't an element, libdom checks
247 * for it. */
248 dom_element_named_parent_node((dom_element *)node,
249 qname->name,
250 (struct dom_element **)parent);
251
252 /* Implementation detail: dom_element_named_parent_node()
253 * increments the reference count of the parent element before
254 * returning it to us. According to docs/RefCnt in the libdom
255 * repository, this will prevent the parent element from being
256 * destroyed if it is pruned from the DOM. That sounds good,
257 * since we don't want to be using a pointer to an object that
258 * has been destroyed... but we also have no way of later
259 * decrementing the reference count ourselves, and don't want
260 * to make the returned node eternal. Decrementing the
261 * reference counter now allows it to be destroyed when the
262 * DOM no longer needs it, and so long as no other parts of
263 * libsvgtiny are messing with the DOM during parsing, that
264 * shouldn't (ha ha) cause any problems. */
265 dom_node_unref(*parent);
266
267 return CSS_OK;
268 }
269
270
271 /**
272 * Find the "next-sibling" of the given element having the given name
273 *
274 * This search corresponds to the "+ foo" combinator in CSS and will
275 * find only "foo" element nodes that immediately precede the given
276 * node under the same parent in the DOM. In CSS the tree is viewed
277 * top-down and in libdom it is viewed from the bottom-up; as a result
278 * "next" and "previous" are sometimes backwards. This is case-sensitive.
279 *
280 * \param pw Pointer to the current SVG parser state
281 * \param node Libdom SVG node
282 * \param qname Name of the sibling node to search for
283 * \param sibling Address at which to store the sibling node pointer
284 *
285 * \return Always returns CSS_OK
286 *
287 * \post If a suitable element is found, a pointer to it will be
288 * stored at the address pointed to by \a sibling; otherwise,
289 * NULL will be stored at the address pointed to by \a sibling
290 */
291 css_error named_sibling_node(void *pw, void *node,
292 const css_qname *qname, void **sibling)
293 {
294 UNUSED(pw);
295 dom_node *n = node; /* the current node */
296 dom_node *prev; /* the previous node */
297 dom_exception err;
298 dom_node_type type;
299 dom_string *name;
300
301 *sibling = NULL; /* default to nothing found */
302
303 /* Begin the search; the first iteration we do outside of the
304 * loop. Implementation detil: dom_node_get_previous_sibling()
305 * increments the reference counter on the returned node. A
306 * comment within named_parent_node() explains why we
307 * decrement it ASAP. */
308 err = dom_node_get_previous_sibling(n, &n);
309 if (err != DOM_NO_ERR) {
310 return CSS_OK;
311 }
312
313 while (n != NULL) {
314 /* We're looking for the first ELEMENT sibling */
315 err = dom_node_get_node_type(n, &type);
316 if (err != DOM_NO_ERR) {
317 dom_node_unref(n);
318 return CSS_OK;
319 }
320
321 if (type == DOM_ELEMENT_NODE) {
322 /* We found an element node, does it have the
323 * right name? */
324 err = dom_node_get_node_name(n, &name);
325 if (err != DOM_NO_ERR) {
326 dom_node_unref(n);
327 return CSS_OK;
328 }
329
330 if (dom_string_lwc_isequal(name,
331 qname->name)) {
332 /* The name is right, return it */
333 *sibling = n;
334 }
335
336 /* There's only one next-sibling element node
337 * and we've already found it, so if its name
338 * wasn't right, we return the default value
339 * of NULL below */
340 dom_string_unref(name);
341 dom_node_unref(n);
342 return CSS_OK;
343 }
344
345 /* Not an element node, so we move on the the previous
346 * previous sibling */
347 err = dom_node_get_previous_sibling(n, &prev);
348 if (err != DOM_NO_ERR) {
349 dom_node_unref(n);
350 return CSS_OK;
351 }
352
353 dom_node_unref(n);
354 n = prev;
355 }
356
357 return CSS_OK;
358 }
359
360
361 /**
362 * Find the first "subsequent-sibling" of the given element having the
363 * given name
364 *
365 * This search corresponds to the "~ foo" combinator in CSS and will
366 * find only "foo" element nodes that precede the given node (under
367 * the same parent) in the DOM. In CSS the tree is viewed top-down and
368 * in libdom it is viewed from the bottom-up; as a result "next" and
369 * "previous" are sometimes backwards. This is case-sensitive.
370 *
371 * \param pw Pointer to the current SVG parser state
372 * \param node Libdom SVG node
373 * \param qname Name of the sibling node to search for
374 * \param sibling Address at which to store the sibling node pointer
375 *
376 * \return Always returns CSS_OK
377 *
378 * \post If a suitable element is found, a pointer to it will be
379 * stored at the address pointed to by \a sibling; otherwise,
380 * NULL will be stored at the address pointed to by \a sibling
381 */
382 css_error named_generic_sibling_node(void *pw, void *node,
383 const css_qname *qname, void **sibling)
384 {
385 UNUSED(pw);
386 dom_node *n = node; /* the current node */
387 dom_node *prev; /* the previous node */
388 dom_exception err;
389 dom_node_type type;
390 dom_string *name;
391
392
393 *sibling = NULL; /* default to nothing found */
394
395 /* Begin the search; the first iteration we do outside of the
396 * loop. Implementation detil: dom_node_get_previous_sibling()
397 * increments the reference counter on the returned node. A
398 * comment within named_parent_node() explains why we
399 * decrement it ASAP. */
400 err = dom_node_get_previous_sibling(n, &n);
401 if (err != DOM_NO_ERR) {
402 return CSS_OK;
403 }
404
405 while (n != NULL) {
406 err = dom_node_get_node_type(n, &type);
407 if (err != DOM_NO_ERR) {
408 dom_node_unref(n);
409 return CSS_OK;
410 }
411
412 if (type == DOM_ELEMENT_NODE) {
413 /* We only want ELEMENT nodes */
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 /* Found one. Save it and stop the search */
423 dom_string_unref(name);
424 dom_node_unref(n);
425 *sibling = n;
426 return CSS_OK;
427 }
428
429 dom_string_unref(name);
430 }
431
432 /* This sibling wasn't an element with the desired
433 name, so move on to the 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 * Return a pointer to the given node's parent
450 *
451 * \param pw Pointer to the current SVG parser state
452 * \param node Libdom SVG node
453 * \param parent Address at which to store the node's parent pointer
454 *
455 * \return Always returns CSS_OK
456 */
457 css_error parent_node(void *pw, void *node, void **parent)
458 {
459 UNUSED(pw);
460 /* Libdom basically implements this for us */
461 dom_element_parent_node(node, (struct dom_element **)parent);
462
463 /* See the comment in named_parent_node() for why we decrement
464 * this reference counter here. */
465 dom_node_unref(*parent);
466
467 return CSS_OK;
468 }
469
470
471 /**
472 * Find the "next-sibling" of the given element
473 *
474 * This search corresponds "+ *" in CSS and will find the first
475 * element node that immediately precedes the given node under the
476 * same parent in the DOM. In CSS the tree is viewed top-down and in
477 * libdom it is viewed from the bottom-up; as a result "next" and
478 * "previous" are sometimes backwards.
479 *
480 * \param pw Pointer to the current SVG parser state
481 * \param node Libdom SVG node
482 * \param sibling Address at which to store the sibling node pointer
483 *
484 * \return Always returns CSS_OK
485 *
486 * \post If a suitable element is found, a pointer to it will be
487 * stored at the address pointed to by \a sibling; otherwise,
488 * NULL will be stored at the address pointed to by \a sibling
489 */
490 css_error sibling_node(void *pw, void *node, void **sibling)
491 {
492 UNUSED(pw);
493 dom_node *n = node; /* the current node */
494 dom_node *prev; /* the previous node */
495 dom_exception err;
496 dom_node_type type;
497
498 *sibling = NULL; /* default to nothing found */
499
500 /* Begin the search; the first iteration we do outside of the
501 * loop. Implementation detil: dom_node_get_previous_sibling()
502 * increments the reference counter on the returned node. A
503 * comment within named_parent_node() explains why we
504 * decrement it ASAP. */
505 err = dom_node_get_previous_sibling(n, &n);
506 if (err != DOM_NO_ERR) {
507 return CSS_OK;
508 }
509
510 while (n != NULL) {
511 err = dom_node_get_node_type(n, &type);
512 if (err != DOM_NO_ERR) {
513 dom_node_unref(n);
514 return CSS_OK;
515 }
516
517 if (type == DOM_ELEMENT_NODE) {
518 /* We found a sibling node that is also an
519 element and that's all we wanted. */
520 *sibling = n;
521 dom_node_unref(n);
522 return CSS_OK;
523 }
524
525 /* This sibling node was not an element; move on to
526 the previous sibling */
527 err = dom_node_get_previous_sibling(n, &prev);
528 if (err != DOM_NO_ERR) {
529 dom_node_unref(n);
530 return CSS_OK;
531 }
532
533 dom_node_unref(n);
534 n = prev;
535 }
536
537 return CSS_OK;
538 }
539
540
541 /**
542 * Test the given node for the given name
543 *
544 * This will return true (via the "match" pointer) if the libdom node
545 * has the given name or if that name is the universal selector;
546 * otherwise it returns false. The comparison is case-sensitive. It
547 * corresponds to a rule like "body { ... }" in CSS.
548 *
549 * \param pw Pointer to the current SVG parser state
550 * \param node Libdom SVG node to test
551 * \param qname Name to check for
552 * \param match Pointer to the test result
553 *
554 * \return Always returns CSS_OK
555 */
556 css_error node_has_name(void *pw, void *node,
557 const css_qname *qname, bool *match)
558 {
559 struct svgtiny_parse_state *state;
560 dom_string *name;
561 dom_exception err;
562
563 /* Start by checking to see if qname is the universal selector */
564 state = (struct svgtiny_parse_state *)pw;
565 if (lwc_string_isequal(qname->name,
566 state->interned_universal, match) == lwc_error_ok) {
567 if (*match) {
568 /* It's the universal selector. In NetSurf, all node
569 * names match the universal selector, and nothing in
570 * the libcss documentation suggests another approach,
571 * so we follow NetSurf here. */
572 return CSS_OK;
573 }
574 }
575
576 err = dom_node_get_node_name((dom_node *)node, &name);
577 if (err != DOM_NO_ERR) {
578 return CSS_OK;
579 }
580
581 /* Unlike with HTML, SVG element names are case-sensitive */
582 *match = dom_string_lwc_isequal(name, qname->name);
583 dom_string_unref(name);
584
585 return CSS_OK;
586 }
587
588
589 /**
590 * Test the given node for the given class
591 *
592 * This will return true (via the "match" pointer) if the libdom node
593 * has the given class. The comparison is case-sensitive. It
594 * corresponds to node.class in CSS.
595 *
596 * \param pw Pointer to the current SVG parser state
597 * \param node Libdom SVG node to test
598 * \param name Class name to check for
599 * \param match Pointer to the test result
600 *
601 * \return Always returns CSS_OK
602 */
603 css_error node_has_class(void *pw, void *node,
604 lwc_string *name, bool *match)
605 {
606 UNUSED(pw);
607 /* libdom implements this for us and apparently it cannot fail */
608 dom_element_has_class((dom_node *)node, name, match);
609 return CSS_OK;
610 }
611
612
613 /**
614 * Test the given node for the given id
615 *
616 * This will return true (via the "match" pointer) if the libdom node
617 * has the given id. The comparison is case-sensitive. It corresponds
618 * to node#id in CSS.
619 *
620 * \param pw Pointer to the current SVG parser state
621 * \param node Libdom SVG node to test
622 * \param name Id to check for
623 * \param match Pointer to the test result
624 *
625 * \return Always returns CSS_OK
626 */
627 css_error node_has_id(void *pw, void *node,
628 lwc_string *name, bool *match)
629 {
630 dom_string *attr;
631 dom_exception err;
632 struct svgtiny_parse_state *state;
633
634 attr = NULL; /* a priori the "id" attribute may not exist */
635 *match = false; /* default to no match */
636
637 state = (struct svgtiny_parse_state *)pw;
638 err = dom_element_get_attribute((dom_node *)node,
639 state->interned_id, &attr);
640 if (err != DOM_NO_ERR || attr == NULL) {
641 return CSS_OK;
642 }
643
644 *match = dom_string_lwc_isequal(attr, name);
645 dom_string_unref(attr);
646
647 return CSS_OK;
648 }
649
650
651 /**
652 * Test the given node for the given attribute
653 *
654 * This will return true (via the "match" pointer) if the libdom node
655 * has an attribute with the given name. The comparison is
656 * case-sensitive. It corresponds to node[attr] in CSS.
657 *
658 * \param pw Pointer to the current SVG parser state
659 * \param node Libdom SVG node to test
660 * \param qname Attribute name to check for
661 * \param match Pointer to the test result
662 *
663 * \return Returns CSS_OK if successful and CSS_NOMEM if anything
664 * goes wrong
665 */
666 css_error node_has_attribute(void *pw, void *node,
667 const css_qname *qname, bool *match)
668 {
669 UNUSED(pw);
670 dom_string *name;
671 dom_exception err;
672
673 /* intern the attribute name as a dom_string so we can
674 * delegate to dom_element_has_attribute() */
675 err = dom_string_create_interned(
676 (const uint8_t *) lwc_string_data(qname->name),
677 lwc_string_length(qname->name),
678 &name);
679 if (err != DOM_NO_ERR) {
680 return CSS_NOMEM;
681 }
682
683 err = dom_element_has_attribute((dom_node *)node, name, match);
684 if (err != DOM_NO_ERR) {
685 dom_string_unref(name);
686 return CSS_OK;
687 }
688
689 dom_string_unref(name);
690 return CSS_OK;
691 }
692
693
694 /**
695 * Test the given node for an attribute with a specific value
696 *
697 * This will return true (via the "match" pointer) if the libdom node
698 * has an attribute with the given name and value. The comparison is
699 * case-sensitive. It corresponds to node[attr=value] in CSS.
700 *
701 * \param pw Pointer to the current SVG parser state
702 * \param node Libdom SVG node to test
703 * \param qname Attribute name to check for
704 * \param value Attribute value to check for
705 * \param match Pointer to the test result
706 *
707 * \return Returns CSS_OK if successful and CSS_NOMEM if we cannot
708 * intern the attribute name (which usually indicates memory
709 * exhaustion)
710 */
711 css_error node_has_attribute_equal(void *pw, void *node,
712 const css_qname *qname, lwc_string *value,
713 bool *match)
714 {
715 /* Implementation note: NetSurf always returns "no match" when
716 * the value is empty (length zero). We allow it, because why
717 * not? */
718
719 UNUSED(pw);
720 dom_string *name;
721 dom_string *attr_val;
722 dom_exception err;
723
724 /* Intern the attribute name as a dom_string so we can
725 * use dom_element_get_attribute() */
726 err = dom_string_create_interned(
727 (const uint8_t *) lwc_string_data(qname->name),
728 lwc_string_length(qname->name),
729 &name);
730 if (err != DOM_NO_ERR) {
731 return CSS_NOMEM;
732 }
733
734 err = dom_element_get_attribute((dom_node *)node, name, &attr_val);
735 if ((err != DOM_NO_ERR) || (attr_val == NULL)) {
736 /* There was an error getting the attribute's value or
737 * the attribute doesn't exist. So, no match? */
738 dom_string_unref(name);
739 *match = false;
740 return CSS_OK;
741 }
742
743 /* Otherwise, we have the attribute value from the given node
744 * and all we need to do is compare. */
745 dom_string_unref(name);
746 *match = dom_string_lwc_isequal(attr_val, value);
747 dom_string_unref(attr_val);
748
749 return CSS_OK;
750 }
751
752
753 /**
754 * Test the given node for an attribute with a specific value,
755 * possibly followed by a single hyphen
756 *
757 * This will return true (via the "match" pointer) if the libdom node
758 * has an attribute with the given name and value or with the given
759 * name and a value that is followed by exactly one hyphen. The
760 * comparison is case-sensitive. This corresponds to [attr|=value]
761 * in CSS.
762 *
763 * \param pw Pointer to the current SVG parser state
764 * \param node Libdom SVG node to test
765 * \param qname Attribute name to check for
766 * \param value Attribute value to check for
767 * \param match Pointer to the test result
768 *
769 * \return Returns CSS_OK if successful and CSS_NOMEM if we cannot
770 * intern the attribute name (which usually indicates memory
771 * exhaustion)
772 */
773 css_error node_has_attribute_dashmatch(void *pw, void *node,
774 const css_qname *qname, lwc_string *value,
775 bool *match)
776 {
777 /* Implementation note: NetSurf always returns "no match" when
778 * the value is empty (length zero). We allow it, because why
779 * not? */
780
781 UNUSED(pw);
782 dom_string *name;
783 dom_string *attr_val;
784 dom_exception err;
785
786 const char *vdata; /* to hold the data underlying "value" */
787 size_t vdata_len;
788 const char *avdata; /* to hold the found attribute value data */
789 size_t avdata_len;
790
791 /* Intern the attribute name as a dom_string so we can
792 * use dom_element_get_attribute() */
793 err = dom_string_create_interned(
794 (const uint8_t *) lwc_string_data(qname->name),
795 lwc_string_length(qname->name),
796 &name);
797 if (err != DOM_NO_ERR) {
798 return CSS_NOMEM;
799 }
800
801 err = dom_element_get_attribute((dom_node *)node, name, &attr_val);
802 if ((err != DOM_NO_ERR) || (attr_val == NULL)) {
803 /* There was an error getting the attribute's value or
804 * the attribute doesn't exist. So, no match? */
805 dom_string_unref(name);
806 *match = false;
807 return CSS_OK;
808 }
809
810 /* Otherwise, we have the attribute value from the given node
811 * and all we need to do is compare. */
812 dom_string_unref(name);
813 *match = dom_string_lwc_isequal(attr_val, value);
814 if (*match) {
815 /* Exact match, we're done */
816 dom_string_unref(attr_val);
817 return CSS_OK;
818 }
819
820 /* No exact match, try it with a hyphen on the end */
821 vdata = lwc_string_data(value); /* needle */
822 vdata_len = lwc_string_length(value);
823 avdata = dom_string_data(attr_val); /* haystack */
824 avdata_len = dom_string_byte_length(attr_val);
825 dom_string_unref(attr_val);
826
827 if (avdata_len > vdata_len && avdata[vdata_len] == '-') {
828 if (strncasecmp(avdata, vdata, vdata_len) == 0) {
829 /* If there's a hyphen in the right position,
830 * it suffices to compare the strings only up
831 * to the hyphen */
832 *match = true;
833 }
834 }
835
836 return CSS_OK;
837 }
838
839
840 /**
841 * Test the given node for an attribute whose value is a
842 * space-separated list of words, one of which is the given word
843 *
844 * This will return true (via the "match" pointer) if the libdom node
845 * has an attribute with the given name and whose value when
846 * considered as a space-separated list of words contains the given
847 * word. The comparison is case-sensitive. This corresponds to
848 * [attr~=value] 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 word Value word 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_includes(void *pw, void *node,
861 const css_qname *qname, lwc_string *word,
862 bool *match)
863 {
864 UNUSED(pw);
865
866 dom_string *name;
867 dom_string *attr_val;
868 dom_exception err;
869 size_t wordlen; /* length of "word" */
870
871 /* pointers used to parse a space-separated list of words */
872 const char *p;
873 const char *start;
874 const char *end;
875
876 *match = false; /* default to no match */
877
878 wordlen = lwc_string_length(word);
879 if (wordlen == 0) {
880 /* In this case, the spec says that "if 'val' is the
881 * empty string, it will never represent anything." */
882 return CSS_OK;
883 }
884
885 /* Intern the attribute name as a dom_string so we can
886 * use dom_element_get_attribute() */
887 err = dom_string_create_interned(
888 (const uint8_t *) lwc_string_data(qname->name),
889 lwc_string_length(qname->name),
890 &name);
891 if (err != DOM_NO_ERR) {
892 return CSS_NOMEM;
893 }
894
895 err = dom_element_get_attribute((dom_node *)node, name, &attr_val);
896 if ((err != DOM_NO_ERR) || (attr_val == NULL)) {
897 /* There was an error getting the attribute's value or
898 * the attribute doesn't exist. So, no match? */
899 dom_string_unref(name);
900 return CSS_OK;
901 }
902
903 /* Parse the list comparing each word against "word" */
904 start = dom_string_data(attr_val);
905 end = start + dom_string_byte_length(attr_val);
906 dom_string_unref(attr_val);
907
908 for (p = start; p <= end; p++) {
909 /* Move forward until we find the end of the first word */
910 if (*p == ' ' || *p == '\0') {
911 /* If the length of that word is the length of the
912 * word we're looking for, do the comparison. */
913 if ((size_t) (p - start) == wordlen &&
914 strncasecmp(start,
915 lwc_string_data(word),
916 wordlen) == 0) {
917 *match = true;
918 break;
919 }
920 /* No match? Set "start" to the beginning of
921 * the next word and loop. */
922 start = p + 1;
923 }
924 }
925
926 return CSS_OK;
927 }
928
929
930 /**
931 * Test the given node for an attribute whose value begins with the
932 * given prefix
933 *
934 * This will return true (via the "match" pointer) if the libdom node
935 * has an attribute with the given name and whose value begins with
936 * the given prefix string. The comparison is case-sensitive. This
937 * corresponds to [attr^=value] in CSS.
938 *
939 * \param pw Pointer to the current SVG parser state
940 * \param node Libdom SVG node to test
941 * \param qname Attribute name to check for
942 * \param prefix Value prefix to check for
943 * \param match Pointer to the test result
944 *
945 * \return Returns CSS_OK if successful and CSS_NOMEM if we cannot
946 * intern the attribute name (which usually indicates memory
947 * exhaustion)
948 */
949 css_error node_has_attribute_prefix(void *pw, void *node,
950 const css_qname *qname, lwc_string *prefix,
951 bool *match)
952 {
953 UNUSED(pw);
954 dom_string *name;
955 dom_string *attr_val;
956 dom_exception err;
957 const char *avdata; /* attribute value data */
958 size_t avdata_len; /* length of that attribute value data */
959 size_t prefixlen; /* length of "prefix" */
960
961 prefixlen = lwc_string_length(prefix);
962 if (prefixlen == 0) {
963 /* In this case, the spec says that "if 'val' is the
964 * empty string, it will never represent anything." */
965 return CSS_OK;
966 }
967
968 /* Intern the attribute name as a dom_string so we can
969 * use dom_element_get_attribute() */
970 err = dom_string_create_interned(
971 (const uint8_t *) lwc_string_data(qname->name),
972 lwc_string_length(qname->name),
973 &name);
974 if (err != DOM_NO_ERR) {
975 return CSS_NOMEM;
976 }
977
978 err = dom_element_get_attribute((dom_node *)node, name, &attr_val);
979 if ((err != DOM_NO_ERR) || (attr_val == NULL)) {
980 /* There was an error getting the attribute's value or
981 * the attribute doesn't exist. So, no match? */
982 dom_string_unref(name);
983 *match = false;
984 return CSS_OK;
985 }
986
987 /* Otherwise, we have the attribute value from the given node,
988 * and the first thing we want to do is check to see if the
989 * whole thing matches the prefix. */
990 dom_string_unref(name);
991 *match = dom_string_lwc_isequal(attr_val, prefix);
992
993 /* If not, check to see if an, uh, prefix matches the
994 * prefix */
995 if (*match == false) {
996 avdata = dom_string_data(attr_val);
997 avdata_len = dom_string_byte_length(attr_val);
998 if ((avdata_len >= prefixlen) &&
999 (strncasecmp(avdata,
1000 lwc_string_data(prefix),
1001 prefixlen) == 0)) {
1002 /* Use strncasecmp to compare only the first
1003 * "n" characters, where "n" is the length of
1004 * the prefix. */
1005 *match = true;
1006 }
1007 }
1008
1009 dom_string_unref(attr_val);
1010
1011 return CSS_OK;
1012 }
1013
1014
1015 /**
1016 * Test the given node for an attribute whose value end with the
1017 * given suffix
1018 *
1019 * This will return true (via the "match" pointer) if the libdom node
1020 * has an attribute with the given name and whose value ends with
1021 * the given suffix string. The comparison is case-sensitive. This
1022 * corresponds to [attr$=value] in CSS.
1023 *
1024 * \param pw Pointer to the current SVG parser state
1025 * \param node Libdom SVG node to test
1026 * \param qname Attribute name to check for
1027 * \param suffix Value suffix to check for
1028 * \param match Pointer to the test result
1029 *
1030 * \return Returns CSS_OK if successful and CSS_NOMEM if we cannot
1031 * intern the attribute name (which usually indicates memory
1032 * exhaustion)
1033 */
1034 css_error node_has_attribute_suffix(void *pw, void *node,
1035 const css_qname *qname, lwc_string *suffix,
1036 bool *match)
1037 {
1038 UNUSED(pw);
1039 dom_string *name;
1040 dom_string *attr_val;
1041 dom_exception err;
1042 const char *avdata; /* attribute value data */
1043 size_t avdata_len; /* length of that attribute value data */
1044 size_t suffixlen; /* length of "suffix" */
1045
1046 /* convenience pointer we'll use when matching the suffix */
1047 const char *suffix_start;
1048
1049 suffixlen = lwc_string_length(suffix);
1050 if (suffixlen == 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 suffix. */
1078 dom_string_unref(name);
1079 *match = dom_string_lwc_isequal(attr_val, suffix);
1080
1081 /* If not, check to see if an, uh, suffix matches the
1082 * suffix */
1083 if (*match == false) {
1084 avdata = dom_string_data(attr_val);
1085 avdata_len = dom_string_byte_length(attr_val);
1086
1087 suffix_start = (char *)(avdata + avdata_len - suffixlen);
1088
1089 if ((avdata_len >= suffixlen) &&
1090 (strncasecmp(suffix_start,
1091 lwc_string_data(suffix),
1092 suffixlen) == 0)) {
1093 /* Use strncasecmp to compare only the last
1094 * "n" characters, where "n" is the length of
1095 * the suffix. */
1096 *match = true;
1097 }
1098 }
1099
1100 dom_string_unref(attr_val);
1101
1102 return CSS_OK;
1103 }
1104
1105
1106 /**
1107 * Test the given node for an attribute whose value contains the
1108 * given substring
1109 *
1110 * This will return true (via the "match" pointer) if the libdom node
1111 * has an attribute with the given name and whose value contains the
1112 * given substring. The comparison is case-sensitive. This corresponds
1113 * to [attr*=value] in CSS.
1114 *
1115 * \param pw Pointer to the current SVG parser state
1116 * \param node Libdom SVG node to test
1117 * \param qname Attribute name to check for
1118 * \param substring Value substring to check for
1119 * \param match Pointer to the test result
1120 *
1121 * \return Returns CSS_OK if successful and CSS_NOMEM if we cannot
1122 * intern the attribute name (which usually indicates memory
1123 * exhaustion)
1124 */
1125 css_error node_has_attribute_substring(void *pw, void *node,
1126 const css_qname *qname, lwc_string *substring,
1127 bool *match)
1128 {
1129 UNUSED(pw);
1130 dom_string *name;
1131 dom_string *attr_val;
1132 dom_exception err;
1133 size_t attr_len; /* length of attr_val */
1134 size_t substrlen; /* length of "substring" */
1135
1136 /* Convenience pointers we use when comparing substrings */
1137 const char *p;
1138 const char *p_max;
1139
1140 substrlen = lwc_string_length(substring);
1141 if (substrlen == 0) {
1142 /* In this case, the spec says that "if 'val' is the
1143 * empty string, it will never represent anything." */
1144 return CSS_OK;
1145 }
1146
1147 /* Intern the attribute name as a dom_string so we can
1148 * use dom_element_get_attribute() */
1149 err = dom_string_create_interned(
1150 (const uint8_t *) lwc_string_data(qname->name),
1151 lwc_string_length(qname->name),
1152 &name);
1153 if (err != DOM_NO_ERR) {
1154 return CSS_NOMEM;
1155 }
1156
1157 err = dom_element_get_attribute((dom_node *)node, name, &attr_val);
1158 if ((err != DOM_NO_ERR) || (attr_val == NULL)) {
1159 /* There was an error getting the attribute's value or
1160 * the attribute doesn't exist. So, no match? */
1161 dom_string_unref(name);
1162 *match = false;
1163 return CSS_OK;
1164 }
1165
1166 /* Otherwise, we have the attribute value from the given node,
1167 * and the first thing we want to do is check to see if the
1168 * whole thing matches the substring. */
1169 dom_string_unref(name);
1170 *match = dom_string_lwc_isequal(attr_val, substring);
1171
1172 /* If not, check to see if an, uh, substring matches the
1173 * substring */
1174 if (*match == false) {
1175 p = dom_string_data(attr_val);
1176
1177 /* Check every long-enough suffix for a prefix match */
1178 attr_len = dom_string_byte_length(attr_val);
1179 if (attr_len >= substrlen) {
1180 p_max = p + attr_len - substrlen;
1181 while (p <= p_max) {
1182 if (strncasecmp(p,
1183 lwc_string_data(substring),
1184 substrlen) == 0) {
1185 *match = true;
1186 break;
1187 }
1188 p++;
1189 }
1190 }
1191 }
1192
1193 dom_string_unref(attr_val);
1194
1195 return CSS_OK;
1196 }
1197
1198
1199 /**
1200 * Test whether or not the given node is the document's root element
1201 * This corresponds to the CSS :root pseudo-selector.
1202 *
1203 * \param pw Pointer to the current SVG parser state
1204 * \param node Libdom SVG node to test
1205 * \param match Pointer to the test result
1206 *
1207 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
1208 */
1209 css_error node_is_root(void *pw, void *node, bool *match)
1210 {
1211 UNUSED(pw);
1212 dom_node *parent;
1213 dom_node_type type;
1214 dom_exception err;
1215
1216 err = dom_node_get_parent_node((dom_node *)node, &parent);
1217 if (err != DOM_NO_ERR) {
1218 return CSS_NOMEM;
1219 }
1220
1221 /* It's the root element if it doesn't have a parent element */
1222 if (parent != NULL) {
1223 err = dom_node_get_node_type(parent, &type);
1224 dom_node_unref(parent);
1225 if (err != DOM_NO_ERR) {
1226 return CSS_NOMEM;
1227 }
1228 if (type != DOM_DOCUMENT_NODE) {
1229 /* DOM_DOCUMENT_NODE is the only allowable
1230 * type of parent node for the root element */
1231 *match = false;
1232 return CSS_OK;
1233 }
1234 }
1235
1236 *match = true;
1237 return CSS_OK;
1238 }
1239
1240
1241 /**
1242 * Used internally in node_count_siblings() to "count" the given
1243 * sibling node. It factors out the node type and name checks.
1244 */
1245 static int node_count_siblings_check(dom_node *dnode,
1246 bool check_name,
1247 dom_string *name)
1248 {
1249 int ret;
1250 dom_node_type type;
1251 dom_exception exc;
1252 dom_string *dnode_name;
1253
1254 /* We flip this to 1 if/when we count this node */
1255 ret = 0;
1256
1257 if (dnode == NULL) {
1258 return ret;
1259 }
1260
1261 exc = dom_node_get_node_type(dnode, &type);
1262 if ((exc != DOM_NO_ERR) || (type != DOM_ELEMENT_NODE)) {
1263 /* We only count element siblings */
1264 return ret;
1265 }
1266
1267 /* ... with the right name */
1268 if (check_name) {
1269 dnode_name = NULL;
1270 exc = dom_node_get_node_name(dnode, &dnode_name);
1271
1272 if ((exc == DOM_NO_ERR) && (dnode_name != NULL)) {
1273 if (dom_string_isequal(name,
1274 dnode_name)) {
1275 ret = 1;
1276 }
1277 dom_string_unref(dnode_name);
1278 }
1279 }
1280 else {
1281 ret = 1;
1282 }
1283
1284 return ret;
1285 }
1286
1287 /**
1288 * Count the given node's sibling elements
1289 *
1290 * This counts the given node's sibling elements in one direction,
1291 * either forwards or backwards, in the DOM. Keep in mind that the
1292 * libdom tree is upside-down compared to the CSS one; so "next" and
1293 * "previous" are actually reversed; the default is to count preceding
1294 * libdom siblings which correspond to subsequent CSS siblings.
1295 *
1296 * This operation is central to the CSS :first-child, :nth-child, and
1297 * :last-child (et cetera) pseudo-selectors.
1298 *
1299 * If same_name is true, then only nodes having the same
1300 * (case-sensitive) name as the given node are counted.
1301 *
1302 * \param pw Pointer to the current SVG parser state
1303 * \param node Libdom SVG node whose siblings we're counting
1304 * \param same_name Whether or not to count only siblings having
1305 * the same name as the given node
1306 * \param after Count subsequent siblings rather than precedent
1307 * ones (the default)
1308 * \param count Pointer to the return value, the number of sibling
1309 * elements
1310 *
1311 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
1312 */
1313 css_error node_count_siblings(void *pw, void *node,
1314 bool same_name, bool after, int32_t *count)
1315 {
1316 UNUSED(pw);
1317 dom_exception exc;
1318 dom_node *dnode; /* node, but with the right type */
1319 dom_string *dnode_name;
1320 dom_node *next; /* "next" sibling (depends on direction) */
1321
1322 /* Pointer to the "next sibling" function */
1323 dom_exception (*next_func)(dom_node *, dom_node **);
1324
1325 *count = 0;
1326
1327 dnode_name = NULL;
1328 dnode = (dom_node *)node;
1329 if (same_name) {
1330 exc = dom_node_get_node_name(dnode, &dnode_name);
1331 if ((exc != DOM_NO_ERR) || (dnode_name == NULL)) {
1332 return CSS_NOMEM;
1333 }
1334 }
1335
1336 /* Increment the reference counter for dnode for as long as
1337 * we retain a reference to it. */
1338 dnode = dom_node_ref(dnode);
1339
1340 next_func = dom_node_get_previous_sibling;
1341 if (after) {
1342 next_func = dom_node_get_next_sibling;
1343 }
1344
1345 do {
1346 exc = next_func(dnode, &next);
1347 if (exc != DOM_NO_ERR) {
1348 break;
1349 }
1350
1351 /* If next_func worked, we're about to swap "next"
1352 * with "dnode" meaning that we will no longer retain
1353 * a reference to the current dnode. */
1354 dom_node_unref(dnode);
1355 dnode = next;
1356
1357 *count += node_count_siblings_check(dnode,
1358 same_name,
1359 dnode_name);
1360 } while (dnode != NULL);
1361
1362 if (dnode_name != NULL) {
1363 dom_string_unref(dnode_name);
1364 }
1365
1366 return CSS_OK;
1367 }
1368
1369
1370 /**
1371 * Determine whether or not the given element is empty
1372 *
1373 * An element is "nonempty" if it has a child that is either an
1374 * element node or a text node.
1375 *
1376 * \param pw Pointer to the current SVG parser state
1377 * \param node Libdom SVG node to check for emptiness
1378 * \param is_empty Pointer to the return value
1379 *
1380 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
1381 */
1382 css_error node_is_empty(void *pw, void *node, bool *is_empty)
1383 {
1384 UNUSED(pw);
1385 dom_node *child; /* current child node pointer */
1386 dom_node *next; /* next child node pointer */
1387 dom_node_type type; /* what type of node is "child" */
1388 dom_exception err;
1389
1390 /* Assume that it's empty by default */
1391 *is_empty = true;
1392
1393 /* Get the given node's first child. Implementation detail:
1394 * this increments the reference counter on the child node. */
1395 err = dom_node_get_first_child((dom_node *)node, &child);
1396 if (err != DOM_NO_ERR) {
1397 return CSS_NOMEM;
1398 }
1399
1400 /* And now loop through all children looking for a
1401 * text/element node. If we find one, the original
1402 * node is "nonempty" */
1403 while (child != NULL) {
1404 err = dom_node_get_node_type(child, &type);
1405 if (err != DOM_NO_ERR) {
1406 dom_node_unref(child);
1407 return CSS_NOMEM;
1408 }
1409
1410 if (type == DOM_ELEMENT_NODE || type == DOM_TEXT_NODE) {
1411 *is_empty = false;
1412 dom_node_unref(child);
1413 return CSS_OK;
1414 }
1415
1416 err = dom_node_get_next_sibling(child, &next);
1417 if (err != DOM_NO_ERR) {
1418 dom_node_unref(child);
1419 return CSS_NOMEM;
1420 }
1421
1422 /* If we're moving to the next node, we can release
1423 * the reference to the current one */
1424 dom_node_unref(child);
1425 child = next;
1426 }
1427
1428 return CSS_OK;
1429 }
1430
1431
1432 /**
1433 * Determine whether or not the given node is a link
1434 *
1435 * A node is a link if it is an element node whose name is "a" and if
1436 * it has an "href" attribute (case-sensitive). This selector
1437 * corresponds to node:link pseudo-class in CSS.
1438 *
1439 * This pseudo-class is a bit awkward because the two standards (HTML5
1440 * and CSS) disagree on what it means, and because libsvgtiny does not
1441 * have enough information to determine if a link has been "visited"
1442 * yet -- that's a UI property. CSS says that :link is for unvisited
1443 * links, which we can't determine. HTML5 says that each link must
1444 * be either a :link or :visited. Since we can't decide either way,
1445 * It seems less wrong to declare that all links are unvisited; i.e.
1446 * that they match :link.
1447 *
1448 * \param pw Pointer to the current SVG parser state
1449 * \param node Libdom SVG node to check
1450 * \param is_link Pointer to the boolean return value
1451 *
1452 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
1453 */
1454 css_error node_is_link(void *pw, void *node, bool *is_link)
1455 {
1456 dom_exception exc;
1457 dom_node *dnode; /* node, but with the right type */
1458 dom_string *dnode_name;
1459 bool has_href;
1460 struct svgtiny_parse_state* state;
1461
1462 dnode = (dom_node *)node;
1463 dnode_name = NULL;
1464 has_href = false; /* assume no href attribute */
1465 *is_link = false; /* assume that it's not a link */
1466
1467 exc = dom_node_get_node_name(dnode, &dnode_name);
1468 if ((exc != DOM_NO_ERR) || (dnode_name == NULL)) {
1469 return CSS_NOMEM;
1470 }
1471
1472 state = (struct svgtiny_parse_state *)pw;
1473 if (dom_string_isequal(dnode_name, state->interned_a)) {
1474 exc = dom_element_has_attribute(node,
1475 state->interned_href,
1476 &has_href);
1477 if (exc == DOM_NO_ERR && has_href) {
1478 *is_link = true;
1479 }
1480 }
1481
1482 dom_string_unref(dnode_name);
1483 return CSS_OK;
1484 }