]> gitweb.michael.orlitzky.com - libsvgtiny.git/blob - src/svgtiny_css.c
3b03d9148273bac2a5bbe97597b514fb60f30a65
[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
37
38 /**
39 * Resolve a relative URL to an absolute one by doing nothing. This is
40 * the simplest possible implementation of a URL resolver, needed for
41 * parsing CSS.
42 */
43 css_error svgtiny_resolve_url(void *pw,
44 const char *base, lwc_string *rel, lwc_string **abs)
45 {
46 UNUSED(pw);
47 UNUSED(base);
48
49 /* Copy the relative URL to the absolute one (the return
50 value) */
51 *abs = lwc_string_ref(rel);
52 return CSS_OK;
53 }
54
55 /**
56 * Create a stylesheet with the default set of params.
57 *
58 * \param sheet A stylesheet pointer, passed in by reference, that
59 * we use to store the newly-created stylesheet.
60 * \param inline_style True if this stylesheet represents an inline
61 * style, and false otherwise.
62 *
63 * \return The return value from css_stylesheet_create() is returned.
64 */
65 css_error svgtiny_create_stylesheet(css_stylesheet **sheet,
66 bool inline_style)
67 {
68 css_stylesheet_params params;
69
70 params.params_version = CSS_STYLESHEET_PARAMS_VERSION_1;
71 params.level = CSS_LEVEL_DEFAULT;
72 params.charset = NULL;
73 params.url = "";
74 params.title = NULL;
75 params.allow_quirks = false;
76 params.inline_style = inline_style;
77 params.resolve = svgtiny_resolve_url;
78 params.resolve_pw = NULL;
79 params.import = NULL;
80 params.import_pw = NULL;
81 params.color = NULL;
82 params.color_pw = NULL;
83 params.font = NULL;
84 params.font_pw = NULL;
85
86 return css_stylesheet_create(&params, sheet);
87 }
88
89
90 /**************************/
91 /* libcss select handlers */
92 /**************************/
93 /*
94 * From here on we implement the "select handler "API defined in
95 * libcss's include/libcss/select.h and discussed briefly in its
96 * docs/API document.
97 */
98
99
100 /**
101 * Retrieve the given node's name
102 *
103 * \param pw Pointer to the current SVG parser state
104 * \param node Libdom SVG node
105 * \param qname Address at which to store the node name
106 *
107 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
108 */
109 css_error node_name(void *pw, void *node, css_qname *qname)
110 {
111 dom_string *name;
112 dom_exception err;
113 struct svgtiny_parse_state *state;
114
115 err = dom_node_get_node_name((dom_node *)node, &name);
116 if (err != DOM_NO_ERR) {
117 return CSS_NOMEM;
118 }
119
120 state = (struct svgtiny_parse_state *)pw;
121 qname->ns = lwc_string_ref(state->interned_svg_xmlns);
122
123 err = dom_string_intern(name, &qname->name);
124 if (err != DOM_NO_ERR) {
125 dom_string_unref(name);
126 return CSS_NOMEM;
127 }
128
129 dom_string_unref(name);
130
131 return CSS_OK;
132 }
133
134
135 /**
136 * Retrieve the given node's classes
137 *
138 * \param pw Pointer to the current SVG parser state
139 * \param node Libdom SVG node
140 * \param classes Address at which to store the class name array
141 * \param n_classes Address at which to store the length of the class
142 * name array
143 *
144 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
145 *
146 * \note CSS_NOMEM is not possible in practice as of libdom-0.4.1,
147 * because the underlying libdom function never fails
148 */
149 css_error node_classes(void *pw, void *node,
150 lwc_string ***classes, uint32_t *n_classes)
151 {
152 UNUSED(pw);
153 dom_exception err;
154
155 err = dom_element_get_classes((dom_node *)node, classes, n_classes);
156
157 /* The implementation does not do it, but the documentation
158 for dom_element_get_classes() says that a DOM_NO_MEM_ERR is
159 possible here, so we handle it to be on the safe side. */
160 if (err != DOM_NO_ERR) {
161 return CSS_NOMEM;
162 }
163
164 return CSS_OK;
165 }
166
167
168 /**
169 * Retrieve the given node's id
170 *
171 * \param pw Pointer to the current SVG parser state
172 * \param node Libdom SVG node
173 * \param id Address at which to store the id
174 *
175 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
176 */
177 css_error node_id(void *pw, void *node, lwc_string **id)
178 {
179 dom_string *attr;
180 dom_exception err;
181 struct svgtiny_parse_state *state;
182
183 /* Begin with the assumption that this node has no id */
184 *id = NULL;
185
186 state = (struct svgtiny_parse_state *)pw;
187 err = dom_element_get_attribute((dom_node *)node,
188 state->interned_id, &attr);
189 if (err != DOM_NO_ERR) {
190 return CSS_NOMEM;
191 }
192 else if (attr == NULL) {
193 /* The node has no id attribute and our return value
194 is already set to NULL so we're done */
195 return CSS_OK;
196 }
197
198 /* If we found an id attribute (a dom_string), intern it into
199 an lwc_string that we can return, and then cleanup the
200 dom_string. */
201 err = dom_string_intern(attr, id);
202 if (err != DOM_NO_ERR) {
203 dom_string_unref(attr);
204 return CSS_NOMEM;
205 }
206 dom_string_unref(attr);
207 return CSS_OK;
208 }
209
210
211
212 /**
213 * Find the first parent of the given element having the given name
214 *
215 * \param pw Pointer to the current SVG parser state
216 * \param node Libdom SVG node
217 * \param qname Name of the parent node to search for
218 * \param parent Address at which to store the parent node pointer
219 *
220 * \return Always returns CSS_OK
221 *
222 * \post If a suitable element is found, a pointer to it will be
223 * stored at the address pointed to by \a parent; otherwise,
224 * NULL will be stored at the address pointed to by \a parent
225 */
226 css_error named_parent_node(void *pw, void *node,
227 const css_qname *qname, void **parent)
228 {
229 UNUSED(pw);
230 /* dom_element_named_parent_node() was invented to implement
231 * this select handler so there isn't much for us to do except
232 * call it. It's OK if node isn't an element, libdom checks
233 * for it. */
234 dom_element_named_parent_node((dom_element *)node,
235 qname->name,
236 (struct dom_element **)parent);
237
238 /* Implementation detail: dom_element_named_parent_node()
239 * increments the reference count of the parent element before
240 * returning it to us. According to docs/RefCnt in the libdom
241 * repository, this will prevent the parent element from being
242 * destroyed if it is pruned from the DOM. That sounds good,
243 * since we don't want to be using a pointer to an object that
244 * has been destroyed... but we also have no way of later
245 * decrementing the reference count ourselves, and don't want
246 * to make the returned node eternal. Decrementing the
247 * reference counter now allows it to be destroyed when the
248 * DOM no longer needs it, and so long as no other parts of
249 * libsvgtiny are messing with the DOM during parsing, that
250 * shouldn't (ha ha) cause any problems. */
251 dom_node_unref(*parent);
252
253 return CSS_OK;
254 }
255
256
257 /**
258 * Find the "next-sibling" of the given element having the given name
259 *
260 * This search corresponds to the "+ foo" combinator in CSS and will
261 * find only "foo" element nodes that immediately precede the given
262 * node under the same parent in the DOM. In CSS the tree is viewed
263 * top-down and in libdom it is viewed from the bottom-up; as a result
264 * "next" and "previous" are sometimes backwards. This is case-sensitive.
265 *
266 * \param pw Pointer to the current SVG parser state
267 * \param node Libdom SVG node
268 * \param qname Name of the sibling node to search for
269 * \param sibling Address at which to store the sibling node pointer
270 *
271 * \return Always returns CSS_OK
272 *
273 * \post If a suitable element is found, a pointer to it will be
274 * stored at the address pointed to by \a sibling; otherwise,
275 * NULL will be stored at the address pointed to by \a sibling
276 */
277 css_error named_sibling_node(void *pw, void *node,
278 const css_qname *qname, void **sibling)
279 {
280 UNUSED(pw);
281 dom_node *n = node; /* the current node */
282 dom_node *prev; /* the previous node */
283 dom_exception err;
284 dom_node_type type;
285 dom_string *name;
286
287 *sibling = NULL; /* default to nothing found */
288
289 /* Begin the search; the first iteration we do outside of the
290 * loop. Implementation detil: dom_node_get_previous_sibling()
291 * increments the reference counter on the returned node. A
292 * comment within named_parent_node() explains why we
293 * decrement it ASAP. */
294 err = dom_node_get_previous_sibling(n, &n);
295 if (err != DOM_NO_ERR) {
296 return CSS_OK;
297 }
298
299 while (n != NULL) {
300 /* We're looking for the first ELEMENT sibling */
301 err = dom_node_get_node_type(n, &type);
302 if (err != DOM_NO_ERR) {
303 dom_node_unref(n);
304 return CSS_OK;
305 }
306
307 if (type == DOM_ELEMENT_NODE) {
308 /* We found an element node, does it have the
309 * right name? */
310 err = dom_node_get_node_name(n, &name);
311 if (err != DOM_NO_ERR) {
312 dom_node_unref(n);
313 return CSS_OK;
314 }
315
316 if (dom_string_lwc_isequal(name,
317 qname->name)) {
318 /* The name is right, return it */
319 *sibling = n;
320 }
321
322 /* There's only one next-sibling element node
323 * and we've already found it, so if its name
324 * wasn't right, we return the default value
325 * of NULL below */
326 dom_string_unref(name);
327 dom_node_unref(n);
328 return CSS_OK;
329 }
330
331 /* Not an element node, so we move on the the previous
332 * previous sibling */
333 err = dom_node_get_previous_sibling(n, &prev);
334 if (err != DOM_NO_ERR) {
335 dom_node_unref(n);
336 return CSS_OK;
337 }
338
339 dom_node_unref(n);
340 n = prev;
341 }
342
343 return CSS_OK;
344 }
345
346
347 /**
348 * Find the first "subsequent-sibling" of the given element having the
349 * given name
350 *
351 * This search corresponds to the "~ foo" combinator in CSS and will
352 * find only "foo" element nodes that precede the given node (under
353 * the same parent) in the DOM. In CSS the tree is viewed top-down and
354 * in libdom it is viewed from the bottom-up; as a result "next" and
355 * "previous" are sometimes backwards. This is case-sensitive.
356 *
357 * \param pw Pointer to the current SVG parser state
358 * \param node Libdom SVG node
359 * \param qname Name of the sibling node to search for
360 * \param sibling Address at which to store the sibling node pointer
361 *
362 * \return Always returns CSS_OK
363 *
364 * \post If a suitable element is found, a pointer to it will be
365 * stored at the address pointed to by \a sibling; otherwise,
366 * NULL will be stored at the address pointed to by \a sibling
367 */
368 css_error named_generic_sibling_node(void *pw, void *node,
369 const css_qname *qname, void **sibling)
370 {
371 UNUSED(pw);
372 dom_node *n = node; /* the current node */
373 dom_node *prev; /* the previous node */
374 dom_exception err;
375 dom_node_type type;
376 dom_string *name;
377
378
379 *sibling = NULL; /* default to nothing found */
380
381 /* Begin the search; the first iteration we do outside of the
382 * loop. Implementation detil: dom_node_get_previous_sibling()
383 * increments the reference counter on the returned node. A
384 * comment within named_parent_node() explains why we
385 * decrement it ASAP. */
386 err = dom_node_get_previous_sibling(n, &n);
387 if (err != DOM_NO_ERR) {
388 return CSS_OK;
389 }
390
391 while (n != NULL) {
392 err = dom_node_get_node_type(n, &type);
393 if (err != DOM_NO_ERR) {
394 dom_node_unref(n);
395 return CSS_OK;
396 }
397
398 if (type == DOM_ELEMENT_NODE) {
399 /* We only want ELEMENT nodes */
400 err = dom_node_get_node_name(n, &name);
401 if (err != DOM_NO_ERR) {
402 dom_node_unref(n);
403 return CSS_OK;
404 }
405
406 if (dom_string_lwc_isequal(name,
407 qname->name)) {
408 /* Found one. Save it and stop the search */
409 dom_string_unref(name);
410 dom_node_unref(n);
411 *sibling = n;
412 return CSS_OK;
413 }
414
415 dom_string_unref(name);
416 }
417
418 /* This sibling wasn't an element with the desired
419 name, so move on to the previous sibling */
420 err = dom_node_get_previous_sibling(n, &prev);
421 if (err != DOM_NO_ERR) {
422 dom_node_unref(n);
423 return CSS_OK;
424 }
425
426 dom_node_unref(n);
427 n = prev;
428 }
429
430 return CSS_OK;
431 }
432
433
434 /**
435 * Return a pointer to the given node's parent
436 *
437 * \param pw Pointer to the current SVG parser state
438 * \param node Libdom SVG node
439 * \param parent Address at which to store the node's parent pointer
440 *
441 * \return Always returns CSS_OK
442 */
443 css_error parent_node(void *pw, void *node, void **parent)
444 {
445 UNUSED(pw);
446 /* Libdom basically implements this for us */
447 dom_element_parent_node(node, (struct dom_element **)parent);
448
449 /* See the comment in named_parent_node() for why we decrement
450 * this reference counter here. */
451 dom_node_unref(*parent);
452
453 return CSS_OK;
454 }
455
456
457 /**
458 * Find the "next-sibling" of the given element
459 *
460 * This search corresponds "+ *" in CSS and will find the first
461 * element node that immediately precedes the given node under the
462 * same parent in the DOM. In CSS the tree is viewed top-down and in
463 * libdom it is viewed from the bottom-up; as a result "next" and
464 * "previous" are sometimes backwards.
465 *
466 * \param pw Pointer to the current SVG parser state
467 * \param node Libdom SVG node
468 * \param sibling Address at which to store the sibling node pointer
469 *
470 * \return Always returns CSS_OK
471 *
472 * \post If a suitable element is found, a pointer to it will be
473 * stored at the address pointed to by \a sibling; otherwise,
474 * NULL will be stored at the address pointed to by \a sibling
475 */
476 css_error sibling_node(void *pw, void *node, void **sibling)
477 {
478 UNUSED(pw);
479 dom_node *n = node; /* the current node */
480 dom_node *prev; /* the previous node */
481 dom_exception err;
482 dom_node_type type;
483
484 *sibling = NULL; /* default to nothing found */
485
486 /* Begin the search; the first iteration we do outside of the
487 * loop. Implementation detil: dom_node_get_previous_sibling()
488 * increments the reference counter on the returned node. A
489 * comment within named_parent_node() explains why we
490 * decrement it ASAP. */
491 err = dom_node_get_previous_sibling(n, &n);
492 if (err != DOM_NO_ERR) {
493 return CSS_OK;
494 }
495
496 while (n != NULL) {
497 err = dom_node_get_node_type(n, &type);
498 if (err != DOM_NO_ERR) {
499 dom_node_unref(n);
500 return CSS_OK;
501 }
502
503 if (type == DOM_ELEMENT_NODE) {
504 /* We found a sibling node that is also an
505 element and that's all we wanted. */
506 *sibling = n;
507 dom_node_unref(n);
508 return CSS_OK;
509 }
510
511 /* This sibling node was not an element; move on to
512 the previous sibling */
513 err = dom_node_get_previous_sibling(n, &prev);
514 if (err != DOM_NO_ERR) {
515 dom_node_unref(n);
516 return CSS_OK;
517 }
518
519 dom_node_unref(n);
520 n = prev;
521 }
522
523 return CSS_OK;
524 }
525
526
527 /**
528 * Test the given node for the given name
529 *
530 * This will return true (via the "match" pointer) if the libdom node
531 * has the given name or if that name is the universal selector;
532 * otherwise it returns false. The comparison is case-sensitive. It
533 * corresponds to a rule like "body { ... }" in CSS.
534 *
535 * \param pw Pointer to the current SVG parser state
536 * \param node Libdom SVG node to test
537 * \param qname Name to check for
538 * \param match Pointer to the test result
539 *
540 * \return Always returns CSS_OK
541 */
542 css_error node_has_name(void *pw, void *node,
543 const css_qname *qname, bool *match)
544 {
545 struct svgtiny_parse_state *state;
546 dom_string *name;
547 dom_exception err;
548
549 /* Start by checking to see if qname is the universal selector */
550 state = (struct svgtiny_parse_state *)pw;
551 if (lwc_string_isequal(qname->name,
552 state->interned_universal, match) == lwc_error_ok) {
553 if (*match) {
554 /* It's the universal selector. In NetSurf, all node
555 * names match the universal selector, and nothing in
556 * the libcss documentation suggests another approach,
557 * so we follow NetSurf here. */
558 return CSS_OK;
559 }
560 }
561
562 err = dom_node_get_node_name((dom_node *)node, &name);
563 if (err != DOM_NO_ERR) {
564 return CSS_OK;
565 }
566
567 /* Unlike with HTML, SVG element names are case-sensitive */
568 *match = dom_string_lwc_isequal(name, qname->name);
569 dom_string_unref(name);
570
571 return CSS_OK;
572 }
573
574
575 /**
576 * Test the given node for the given class
577 *
578 * This will return true (via the "match" pointer) if the libdom node
579 * has the given class. The comparison is case-sensitive. It
580 * corresponds to node.class in CSS.
581 *
582 * \param pw Pointer to the current SVG parser state
583 * \param node Libdom SVG node to test
584 * \param name Class name to check for
585 * \param match Pointer to the test result
586 *
587 * \return Always returns CSS_OK
588 */
589 css_error node_has_class(void *pw, void *node,
590 lwc_string *name, bool *match)
591 {
592 UNUSED(pw);
593 /* libdom implements this for us and apparently it cannot fail */
594 dom_element_has_class((dom_node *)node, name, match);
595 return CSS_OK;
596 }
597
598
599 /**
600 * Test the given node for the given id
601 *
602 * This will return true (via the "match" pointer) if the libdom node
603 * has the given id. The comparison is case-sensitive. It corresponds
604 * to node#id in CSS.
605 *
606 * \param pw Pointer to the current SVG parser state
607 * \param node Libdom SVG node to test
608 * \param name Id to check for
609 * \param match Pointer to the test result
610 *
611 * \return Always returns CSS_OK
612 */
613 css_error node_has_id(void *pw, void *node,
614 lwc_string *name, bool *match)
615 {
616 dom_string *attr;
617 dom_exception err;
618 struct svgtiny_parse_state *state;
619
620 attr = NULL; /* a priori the "id" attribute may not exist */
621 *match = false; /* default to no match */
622
623 state = (struct svgtiny_parse_state *)pw;
624 err = dom_element_get_attribute((dom_node *)node,
625 state->interned_id, &attr);
626 if (err != DOM_NO_ERR || attr == NULL) {
627 return CSS_OK;
628 }
629
630 *match = dom_string_lwc_isequal(attr, name);
631 dom_string_unref(attr);
632
633 return CSS_OK;
634 }
635
636
637 /**
638 * Test the given node for the given attribute
639 *
640 * This will return true (via the "match" pointer) if the libdom node
641 * has an attribute with the given name. The comparison is
642 * case-sensitive. It corresponds to node[attr] in CSS.
643 *
644 * \param pw Pointer to the current SVG parser state
645 * \param node Libdom SVG node to test
646 * \param qname Attribute name to check for
647 * \param match Pointer to the test result
648 *
649 * \return Returns CSS_OK if successful and CSS_NOMEM if anything
650 * goes wrong
651 */
652 css_error node_has_attribute(void *pw, void *node,
653 const css_qname *qname, bool *match)
654 {
655 UNUSED(pw);
656 dom_string *name;
657 dom_exception err;
658
659 /* intern the attribute name as a dom_string so we can
660 * delegate to dom_element_has_attribute() */
661 err = dom_string_create_interned(
662 (const uint8_t *) lwc_string_data(qname->name),
663 lwc_string_length(qname->name),
664 &name);
665 if (err != DOM_NO_ERR) {
666 return CSS_NOMEM;
667 }
668
669 err = dom_element_has_attribute((dom_node *)node, name, match);
670 if (err != DOM_NO_ERR) {
671 dom_string_unref(name);
672 return CSS_OK;
673 }
674
675 dom_string_unref(name);
676 return CSS_OK;
677 }
678
679
680 /**
681 * Test the given node for an attribute with a specific value
682 *
683 * This will return true (via the "match" pointer) if the libdom node
684 * has an attribute with the given name and value. The comparison is
685 * case-sensitive. It corresponds to node[attr=value] in CSS.
686 *
687 * \param pw Pointer to the current SVG parser state
688 * \param node Libdom SVG node to test
689 * \param qname Attribute name to check for
690 * \param value Attribute value to check for
691 * \param match Pointer to the test result
692 *
693 * \return Returns CSS_OK if successful and CSS_NOMEM if we cannot
694 * intern the attribute name (which usually indicates memory
695 * exhaustion)
696 */
697 css_error node_has_attribute_equal(void *pw, void *node,
698 const css_qname *qname, lwc_string *value,
699 bool *match)
700 {
701 /* Implementation note: NetSurf always returns "no match" when
702 * the value is empty (length zero). We allow it, because why
703 * not? */
704
705 UNUSED(pw);
706 dom_string *name;
707 dom_string *attr_val;
708 dom_exception err;
709
710 /* Intern the attribute name as a dom_string so we can
711 * use dom_element_get_attribute() */
712 err = dom_string_create_interned(
713 (const uint8_t *) lwc_string_data(qname->name),
714 lwc_string_length(qname->name),
715 &name);
716 if (err != DOM_NO_ERR) {
717 return CSS_NOMEM;
718 }
719
720 err = dom_element_get_attribute((dom_node *)node, name, &attr_val);
721 if ((err != DOM_NO_ERR) || (attr_val == NULL)) {
722 /* There was an error getting the attribute's value or
723 * the attribute doesn't exist. So, no match? */
724 dom_string_unref(name);
725 *match = false;
726 return CSS_OK;
727 }
728
729 /* Otherwise, we have the attribute value from the given node
730 * and all we need to do is compare. */
731 dom_string_unref(name);
732 *match = dom_string_lwc_isequal(attr_val, value);
733 dom_string_unref(attr_val);
734
735 return CSS_OK;
736 }
737
738
739 /**
740 * Test the given node for an attribute with a specific value,
741 * possibly followed by a single hyphen
742 *
743 * This will return true (via the "match" pointer) if the libdom node
744 * has an attribute with the given name and value or with the given
745 * name and a value that is followed by exactly one hyphen. The
746 * comparison is case-sensitive. This corresponds to [attr|=value]
747 * in CSS.
748 *
749 * \param pw Pointer to the current SVG parser state
750 * \param node Libdom SVG node to test
751 * \param qname Attribute name to check for
752 * \param value Attribute value to check for
753 * \param match Pointer to the test result
754 *
755 * \return Returns CSS_OK if successful and CSS_NOMEM if we cannot
756 * intern the attribute name (which usually indicates memory
757 * exhaustion)
758 */
759 css_error node_has_attribute_dashmatch(void *pw, void *node,
760 const css_qname *qname, lwc_string *value,
761 bool *match)
762 {
763 /* Implementation note: NetSurf always returns "no match" when
764 * the value is empty (length zero). We allow it, because why
765 * not? */
766
767 UNUSED(pw);
768 dom_string *name;
769 dom_string *attr_val;
770 dom_exception err;
771
772 const char *vdata; /* to hold the data underlying "value" */
773 size_t vdata_len;
774 const char *avdata; /* to hold the found attribute value data */
775 size_t avdata_len;
776
777 /* Intern the attribute name as a dom_string so we can
778 * use dom_element_get_attribute() */
779 err = dom_string_create_interned(
780 (const uint8_t *) lwc_string_data(qname->name),
781 lwc_string_length(qname->name),
782 &name);
783 if (err != DOM_NO_ERR) {
784 return CSS_NOMEM;
785 }
786
787 err = dom_element_get_attribute((dom_node *)node, name, &attr_val);
788 if ((err != DOM_NO_ERR) || (attr_val == NULL)) {
789 /* There was an error getting the attribute's value or
790 * the attribute doesn't exist. So, no match? */
791 dom_string_unref(name);
792 *match = false;
793 return CSS_OK;
794 }
795
796 /* Otherwise, we have the attribute value from the given node
797 * and all we need to do is compare. */
798 dom_string_unref(name);
799 *match = dom_string_lwc_isequal(attr_val, value);
800 if (*match) {
801 /* Exact match, we're done */
802 dom_string_unref(attr_val);
803 return CSS_OK;
804 }
805
806 /* No exact match, try it with a hyphen on the end */
807 vdata = lwc_string_data(value); /* needle */
808 vdata_len = lwc_string_length(value);
809 avdata = dom_string_data(attr_val); /* haystack */
810 avdata_len = dom_string_byte_length(attr_val);
811 dom_string_unref(attr_val);
812
813 if (avdata_len > vdata_len && avdata[vdata_len] == '-') {
814 if (strncasecmp(avdata, vdata, vdata_len) == 0) {
815 /* If there's a hyphen in the right position,
816 * it suffices to compare the strings only up
817 * to the hyphen */
818 *match = true;
819 }
820 }
821
822 return CSS_OK;
823 }
824
825
826 /**
827 * Test the given node for an attribute whose value is a
828 * space-separated list of words, one of which is the given word
829 *
830 * This will return true (via the "match" pointer) if the libdom node
831 * has an attribute with the given name and whose value when
832 * considered as a space-separated list of words contains the given
833 * word. The comparison is case-sensitive. This corresponds to
834 * [attr~=value] in CSS.
835 *
836 * \param pw Pointer to the current SVG parser state
837 * \param node Libdom SVG node to test
838 * \param qname Attribute name to check for
839 * \param word Value word to check for
840 * \param match Pointer to the test result
841 *
842 * \return Returns CSS_OK if successful and CSS_NOMEM if we cannot
843 * intern the attribute name (which usually indicates memory
844 * exhaustion)
845 */
846 css_error node_has_attribute_includes(void *pw, void *node,
847 const css_qname *qname, lwc_string *word,
848 bool *match)
849 {
850 UNUSED(pw);
851
852 dom_string *name;
853 dom_string *attr_val;
854 dom_exception err;
855 size_t wordlen; /* length of "word" */
856
857 /* pointers used to parse a space-separated list of words */
858 const char *p;
859 const char *start;
860 const char *end;
861
862 *match = false; /* default to no match */
863
864 wordlen = lwc_string_length(word);
865 if (wordlen == 0) {
866 /* In this case, the spec says that "if 'val' is the
867 * empty string, it will never represent anything." */
868 return CSS_OK;
869 }
870
871 /* Intern the attribute name as a dom_string so we can
872 * use dom_element_get_attribute() */
873 err = dom_string_create_interned(
874 (const uint8_t *) lwc_string_data(qname->name),
875 lwc_string_length(qname->name),
876 &name);
877 if (err != DOM_NO_ERR) {
878 return CSS_NOMEM;
879 }
880
881 err = dom_element_get_attribute((dom_node *)node, name, &attr_val);
882 if ((err != DOM_NO_ERR) || (attr_val == NULL)) {
883 /* There was an error getting the attribute's value or
884 * the attribute doesn't exist. So, no match? */
885 dom_string_unref(name);
886 return CSS_OK;
887 }
888
889 /* Parse the list comparing each word against "word" */
890 start = dom_string_data(attr_val);
891 end = start + dom_string_byte_length(attr_val);
892 dom_string_unref(attr_val);
893
894 for (p = start; p <= end; p++) {
895 /* Move forward until we find the end of the first word */
896 if (*p == ' ' || *p == '\0') {
897 /* If the length of that word is the length of the
898 * word we're looking for, do the comparison. */
899 if ((size_t) (p - start) == wordlen &&
900 strncasecmp(start,
901 lwc_string_data(word),
902 wordlen) == 0) {
903 *match = true;
904 break;
905 }
906 /* No match? Set "start" to the beginning of
907 * the next word and loop. */
908 start = p + 1;
909 }
910 }
911
912 return CSS_OK;
913 }