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