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