]> gitweb.michael.orlitzky.com - libsvgtiny.git/blob - src/svgtiny_css.c
src/svgtiny_css.c: implement node_has_class() select handler
[libsvgtiny.git] / src / svgtiny_css.c
1 #include <libcss/libcss.h>
2
3 #include "svgtiny.h"
4 #include "svgtiny_internal.h"
5
6 static css_error node_name(void *pw, void *node, css_qname *qname);
7 static css_error node_classes(void *pw, void *node,
8 lwc_string ***classes, uint32_t *n_classes);
9 static css_error node_id(void *pw, void *node, lwc_string **id);
10 static css_error named_parent_node(void *pw, void *node,
11 const css_qname *qname, void **parent);
12 static css_error named_sibling_node(void *pw, void *node,
13 const css_qname *qname, void **sibling);
14 static css_error named_generic_sibling_node(void *pw, void *node,
15 const css_qname *qname, void **sibling);
16 static css_error parent_node(void *pw, void *node, void **parent);
17 static css_error sibling_node(void *pw, void *node, void **sibling);
18 static css_error node_has_name(void *pw, void *node,
19 const css_qname *qname, bool *match);
20 static css_error node_has_class(void *pw, void *node,
21 lwc_string *name, bool *match);
22
23
24 /**
25 * Resolve a relative URL to an absolute one by doing nothing. This is
26 * the simplest possible implementation of a URL resolver, needed for
27 * parsing CSS.
28 */
29 css_error svgtiny_resolve_url(void *pw,
30 const char *base, lwc_string *rel, lwc_string **abs)
31 {
32 UNUSED(pw);
33 UNUSED(base);
34
35 /* Copy the relative URL to the absolute one (the return
36 value) */
37 *abs = lwc_string_ref(rel);
38 return CSS_OK;
39 }
40
41 /**
42 * Create a stylesheet with the default set of params.
43 *
44 * \param sheet A stylesheet pointer, passed in by reference, that
45 * we use to store the newly-created stylesheet.
46 * \param inline_style True if this stylesheet represents an inline
47 * style, and false otherwise.
48 *
49 * \return The return value from css_stylesheet_create() is returned.
50 */
51 css_error svgtiny_create_stylesheet(css_stylesheet **sheet,
52 bool inline_style)
53 {
54 css_stylesheet_params params;
55
56 params.params_version = CSS_STYLESHEET_PARAMS_VERSION_1;
57 params.level = CSS_LEVEL_DEFAULT;
58 params.charset = NULL;
59 params.url = "";
60 params.title = NULL;
61 params.allow_quirks = false;
62 params.inline_style = inline_style;
63 params.resolve = svgtiny_resolve_url;
64 params.resolve_pw = NULL;
65 params.import = NULL;
66 params.import_pw = NULL;
67 params.color = NULL;
68 params.color_pw = NULL;
69 params.font = NULL;
70 params.font_pw = NULL;
71
72 return css_stylesheet_create(&params, sheet);
73 }
74
75
76 /**************************/
77 /* libcss select handlers */
78 /**************************/
79 /*
80 * From here on we implement the "select handler "API defined in
81 * libcss's include/libcss/select.h and discussed briefly in its
82 * docs/API document.
83 */
84
85
86 /**
87 * Retrieve the given node's name
88 *
89 * \param pw Pointer to the current SVG parser state
90 * \param node Libdom SVG node
91 * \param qname Address at which to store the node name
92 *
93 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
94 */
95 css_error node_name(void *pw, void *node, css_qname *qname)
96 {
97 dom_string *name;
98 dom_exception err;
99 struct svgtiny_parse_state *state;
100
101 err = dom_node_get_node_name((dom_node *)node, &name);
102 if (err != DOM_NO_ERR) {
103 return CSS_NOMEM;
104 }
105
106 state = (struct svgtiny_parse_state *)pw;
107 qname->ns = lwc_string_ref(state->interned_svg_xmlns);
108
109 err = dom_string_intern(name, &qname->name);
110 if (err != DOM_NO_ERR) {
111 dom_string_unref(name);
112 return CSS_NOMEM;
113 }
114
115 dom_string_unref(name);
116
117 return CSS_OK;
118 }
119
120
121 /**
122 * Retrieve the given node's classes
123 *
124 * \param pw Pointer to the current SVG parser state
125 * \param node Libdom SVG node
126 * \param classes Address at which to store the class name array
127 * \param n_classes Address at which to store the length of the class
128 * name array
129 *
130 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
131 *
132 * \note CSS_NOMEM is not possible in practice as of libdom-0.4.1,
133 * because the underlying libdom function never fails
134 */
135 css_error node_classes(void *pw, void *node,
136 lwc_string ***classes, uint32_t *n_classes)
137 {
138 UNUSED(pw);
139 dom_exception err;
140
141 err = dom_element_get_classes((dom_node *)node, classes, n_classes);
142
143 /* The implementation does not do it, but the documentation
144 for dom_element_get_classes() says that a DOM_NO_MEM_ERR is
145 possible here, so we handle it to be on the safe side. */
146 if (err != DOM_NO_ERR) {
147 return CSS_NOMEM;
148 }
149
150 return CSS_OK;
151 }
152
153
154 /**
155 * Retrieve the given node's id
156 *
157 * \param pw Pointer to the current SVG parser state
158 * \param node Libdom SVG node
159 * \param id Address at which to store the id
160 *
161 * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong
162 */
163 css_error node_id(void *pw, void *node, lwc_string **id)
164 {
165 dom_string *attr;
166 dom_exception err;
167 struct svgtiny_parse_state *state;
168
169 /* Begin with the assumption that this node has no id */
170 *id = NULL;
171
172 state = (struct svgtiny_parse_state *)pw;
173 err = dom_element_get_attribute((dom_node *)node,
174 state->interned_id, &attr);
175 if (err != DOM_NO_ERR) {
176 return CSS_NOMEM;
177 }
178 else if (attr == NULL) {
179 /* The node has no id attribute and our return value
180 is already set to NULL so we're done */
181 return CSS_OK;
182 }
183
184 /* If we found an id attribute (a dom_string), intern it into
185 an lwc_string that we can return, and then cleanup the
186 dom_string. */
187 err = dom_string_intern(attr, id);
188 if (err != DOM_NO_ERR) {
189 dom_string_unref(attr);
190 return CSS_NOMEM;
191 }
192 dom_string_unref(attr);
193 return CSS_OK;
194 }
195
196
197
198 /**
199 * Find the first parent of the given element having the given name
200 *
201 * \param pw Pointer to the current SVG parser state
202 * \param node Libdom SVG node
203 * \param qname Name of the parent node to search for
204 * \param parent Address at which to store the parent node pointer
205 *
206 * \return Always returns CSS_OK
207 *
208 * \post If a suitable element is found, a pointer to it will be
209 * stored at the address pointed to by \a parent; otherwise,
210 * NULL will be stored at the address pointed to by \a parent
211 */
212 css_error named_parent_node(void *pw, void *node,
213 const css_qname *qname, void **parent)
214 {
215 UNUSED(pw);
216 /* dom_element_named_parent_node() was invented to implement
217 * this select handler so there isn't much for us to do except
218 * call it. It's OK if node isn't an element, libdom checks
219 * for it. */
220 dom_element_named_parent_node((dom_element *)node,
221 qname->name,
222 (struct dom_element **)parent);
223
224 /* Implementation detail: dom_element_named_parent_node()
225 * increments the reference count of the parent element before
226 * returning it to us. According to docs/RefCnt in the libdom
227 * repository, this will prevent the parent element from being
228 * destroyed if it is pruned from the DOM. That sounds good,
229 * since we don't want to be using a pointer to an object that
230 * has been destroyed... but we also have no way of later
231 * decrementing the reference count ourselves, and don't want
232 * to make the returned node eternal. Decrementing the
233 * reference counter now allows it to be destroyed when the
234 * DOM no longer needs it, and so long as no other parts of
235 * libsvgtiny are messing with the DOM during parsing, that
236 * shouldn't (ha ha) cause any problems. */
237 dom_node_unref(*parent);
238
239 return CSS_OK;
240 }
241
242
243 /**
244 * Find the "next-sibling" of the given element having the given name
245 *
246 * This search corresponds to the "+ foo" combinator in CSS and will
247 * find only "foo" element nodes that immediately precede the given
248 * node under the same parent in the DOM. In CSS the tree is viewed
249 * top-down and in libdom it is viewed from the bottom-up; as a result
250 * "next" and "previous" are sometimes backwards. This is case-sensitive.
251 *
252 * \param pw Pointer to the current SVG parser state
253 * \param node Libdom SVG node
254 * \param qname Name of the sibling node to search for
255 * \param sibling Address at which to store the sibling node pointer
256 *
257 * \return Always returns CSS_OK
258 *
259 * \post If a suitable element is found, a pointer to it will be
260 * stored at the address pointed to by \a sibling; otherwise,
261 * NULL will be stored at the address pointed to by \a sibling
262 */
263 css_error named_sibling_node(void *pw, void *node,
264 const css_qname *qname, void **sibling)
265 {
266 UNUSED(pw);
267 dom_node *n = node; /* the current node */
268 dom_node *prev; /* the previous node */
269 dom_exception err;
270 dom_node_type type;
271 dom_string *name;
272
273 *sibling = NULL; /* default to nothing found */
274
275 /* Begin the search; the first iteration we do outside of the
276 * loop. Implementation detil: dom_node_get_previous_sibling()
277 * increments the reference counter on the returned node. A
278 * comment within named_parent_node() explains why we
279 * decrement it ASAP. */
280 err = dom_node_get_previous_sibling(n, &n);
281 if (err != DOM_NO_ERR) {
282 return CSS_OK;
283 }
284
285 while (n != NULL) {
286 /* We're looking for the first ELEMENT sibling */
287 err = dom_node_get_node_type(n, &type);
288 if (err != DOM_NO_ERR) {
289 dom_node_unref(n);
290 return CSS_OK;
291 }
292
293 if (type == DOM_ELEMENT_NODE) {
294 /* We found an element node, does it have the
295 * right name? */
296 err = dom_node_get_node_name(n, &name);
297 if (err != DOM_NO_ERR) {
298 dom_node_unref(n);
299 return CSS_OK;
300 }
301
302 if (dom_string_lwc_isequal(name,
303 qname->name)) {
304 /* The name is right, return it */
305 *sibling = n;
306 }
307
308 /* There's only one next-sibling element node
309 * and we've already found it, so if its name
310 * wasn't right, we return the default value
311 * of NULL below */
312 dom_string_unref(name);
313 dom_node_unref(n);
314 return CSS_OK;
315 }
316
317 /* Not an element node, so we move on the the previous
318 * previous sibling */
319 err = dom_node_get_previous_sibling(n, &prev);
320 if (err != DOM_NO_ERR) {
321 dom_node_unref(n);
322 return CSS_OK;
323 }
324
325 dom_node_unref(n);
326 n = prev;
327 }
328
329 return CSS_OK;
330 }
331
332
333 /**
334 * Find the first "subsequent-sibling" of the given element having the
335 * given name
336 *
337 * This search corresponds to the "~ foo" combinator in CSS and will
338 * find only "foo" element nodes that precede the given node (under
339 * the same parent) in the DOM. In CSS the tree is viewed top-down and
340 * in libdom it is viewed from the bottom-up; as a result "next" and
341 * "previous" are sometimes backwards. This is case-sensitive.
342 *
343 * \param pw Pointer to the current SVG parser state
344 * \param node Libdom SVG node
345 * \param qname Name of the sibling node to search for
346 * \param sibling Address at which to store the sibling node pointer
347 *
348 * \return Always returns CSS_OK
349 *
350 * \post If a suitable element is found, a pointer to it will be
351 * stored at the address pointed to by \a sibling; otherwise,
352 * NULL will be stored at the address pointed to by \a sibling
353 */
354 css_error named_generic_sibling_node(void *pw, void *node,
355 const css_qname *qname, void **sibling)
356 {
357 UNUSED(pw);
358 dom_node *n = node; /* the current node */
359 dom_node *prev; /* the previous node */
360 dom_exception err;
361 dom_node_type type;
362 dom_string *name;
363
364
365 *sibling = NULL; /* default to nothing found */
366
367 /* Begin the search; the first iteration we do outside of the
368 * loop. Implementation detil: dom_node_get_previous_sibling()
369 * increments the reference counter on the returned node. A
370 * comment within named_parent_node() explains why we
371 * decrement it ASAP. */
372 err = dom_node_get_previous_sibling(n, &n);
373 if (err != DOM_NO_ERR) {
374 return CSS_OK;
375 }
376
377 while (n != NULL) {
378 err = dom_node_get_node_type(n, &type);
379 if (err != DOM_NO_ERR) {
380 dom_node_unref(n);
381 return CSS_OK;
382 }
383
384 if (type == DOM_ELEMENT_NODE) {
385 /* We only want ELEMENT nodes */
386 err = dom_node_get_node_name(n, &name);
387 if (err != DOM_NO_ERR) {
388 dom_node_unref(n);
389 return CSS_OK;
390 }
391
392 if (dom_string_lwc_isequal(name,
393 qname->name)) {
394 /* Found one. Save it and stop the search */
395 dom_string_unref(name);
396 dom_node_unref(n);
397 *sibling = n;
398 return CSS_OK;
399 }
400
401 dom_string_unref(name);
402 }
403
404 /* This sibling wasn't an element with the desired
405 name, so move on to the previous sibling */
406 err = dom_node_get_previous_sibling(n, &prev);
407 if (err != DOM_NO_ERR) {
408 dom_node_unref(n);
409 return CSS_OK;
410 }
411
412 dom_node_unref(n);
413 n = prev;
414 }
415
416 return CSS_OK;
417 }
418
419
420 /**
421 * Return a pointer to the given node's parent
422 *
423 * \param pw Pointer to the current SVG parser state
424 * \param node Libdom SVG node
425 * \param parent Address at which to store the node's parent pointer
426 *
427 * \return Always returns CSS_OK
428 */
429 css_error parent_node(void *pw, void *node, void **parent)
430 {
431 UNUSED(pw);
432 /* Libdom basically implements this for us */
433 dom_element_parent_node(node, (struct dom_element **)parent);
434
435 /* See the comment in named_parent_node() for why we decrement
436 * this reference counter here. */
437 dom_node_unref(*parent);
438
439 return CSS_OK;
440 }
441
442
443 /**
444 * Find the "next-sibling" of the given element
445 *
446 * This search corresponds "+ *" in CSS and will find the first
447 * element node that immediately precedes the given node under the
448 * same parent in the DOM. In CSS the tree is viewed top-down and in
449 * libdom it is viewed from the bottom-up; as a result "next" and
450 * "previous" are sometimes backwards.
451 *
452 * \param pw Pointer to the current SVG parser state
453 * \param node Libdom SVG node
454 * \param sibling Address at which to store the sibling node pointer
455 *
456 * \return Always returns CSS_OK
457 *
458 * \post If a suitable element is found, a pointer to it will be
459 * stored at the address pointed to by \a sibling; otherwise,
460 * NULL will be stored at the address pointed to by \a sibling
461 */
462 css_error sibling_node(void *pw, void *node, void **sibling)
463 {
464 UNUSED(pw);
465 dom_node *n = node; /* the current node */
466 dom_node *prev; /* the previous node */
467 dom_exception err;
468 dom_node_type type;
469
470 *sibling = NULL; /* default to nothing found */
471
472 /* Begin the search; the first iteration we do outside of the
473 * loop. Implementation detil: dom_node_get_previous_sibling()
474 * increments the reference counter on the returned node. A
475 * comment within named_parent_node() explains why we
476 * decrement it ASAP. */
477 err = dom_node_get_previous_sibling(n, &n);
478 if (err != DOM_NO_ERR) {
479 return CSS_OK;
480 }
481
482 while (n != NULL) {
483 err = dom_node_get_node_type(n, &type);
484 if (err != DOM_NO_ERR) {
485 dom_node_unref(n);
486 return CSS_OK;
487 }
488
489 if (type == DOM_ELEMENT_NODE) {
490 /* We found a sibling node that is also an
491 element and that's all we wanted. */
492 *sibling = n;
493 dom_node_unref(n);
494 return CSS_OK;
495 }
496
497 /* This sibling node was not an element; move on to
498 the previous sibling */
499 err = dom_node_get_previous_sibling(n, &prev);
500 if (err != DOM_NO_ERR) {
501 dom_node_unref(n);
502 return CSS_OK;
503 }
504
505 dom_node_unref(n);
506 n = prev;
507 }
508
509 return CSS_OK;
510 }
511
512
513 /**
514 * Test the given node for the given name
515 *
516 * This will return true (via the "match" pointer) if the libdom node
517 * has the given name or if that name is the universal selector;
518 * otherwise it returns false. The comparison is case-sensitive. It
519 * corresponds to a rule like "body { ... }" in CSS.
520 *
521 * \param pw Pointer to the current SVG parser state
522 * \param node Libdom SVG node to test
523 * \param qname Name to check for
524 * \param match Pointer to the test result
525 *
526 * \return Always returns CSS_OK
527 */
528 css_error node_has_name(void *pw, void *node,
529 const css_qname *qname, bool *match)
530 {
531 struct svgtiny_parse_state *state;
532 dom_string *name;
533 dom_exception err;
534
535 /* Start by checking to see if qname is the universal selector */
536 state = (struct svgtiny_parse_state *)pw;
537 if (lwc_string_isequal(qname->name,
538 state->interned_universal, match) == lwc_error_ok) {
539 if (*match) {
540 /* It's the universal selector. In NetSurf, all node
541 * names match the universal selector, and nothing in
542 * the libcss documentation suggests another approach,
543 * so we follow NetSurf here. */
544 return CSS_OK;
545 }
546 }
547
548 err = dom_node_get_node_name((dom_node *)node, &name);
549 if (err != DOM_NO_ERR) {
550 return CSS_OK;
551 }
552
553 /* Unlike with HTML, SVG element names are case-sensitive */
554 *match = dom_string_lwc_isequal(name, qname->name);
555 dom_string_unref(name);
556
557 return CSS_OK;
558 }
559
560
561 /**
562 * Test the given node for the given class
563 *
564 * This will return true (via the "match" pointer) if the libdom node
565 * has the given class. The comparison is case-sensitive. It
566 * corresponds to node.class in CSS.
567 *
568 * \param pw Pointer to the current SVG parser state
569 * \param node Libdom SVG node to test
570 * \param name Class name to check for
571 * \param match Pointer to the test result
572 *
573 * \return Always returns CSS_OK
574 */
575 css_error node_has_class(void *pw, void *node,
576 lwc_string *name, bool *match)
577 {
578 UNUSED(pw);
579 /* libdom implements this for us and apparently it cannot fail */
580 dom_element_has_class((dom_node *)node, name, match);
581 return CSS_OK;
582 }