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