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