Add support for parsing space separated CSS class attribute values
[elinks.git] / src / document / css / apply.c
blobb419c878d8517ff5fd68e856cecc7118927238b6
1 /* CSS style applier */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <stdlib.h>
8 #include <string.h>
10 #include "elinks.h"
12 #include "document/css/apply.h"
13 #include "document/css/css.h"
14 #include "document/css/parser.h"
15 #include "document/css/property.h"
16 #include "document/css/scanner.h"
17 #include "document/css/stylesheet.h"
18 #include "document/html/parser/parse.h"
19 #include "document/options.h"
20 #include "util/align.h"
21 #include "util/color.h"
22 #include "util/lists.h"
23 #include "util/error.h"
24 #include "util/memory.h"
25 #include "util/string.h"
27 /* XXX: Some strange dependency makes it necessary to this include last. */
28 #include "document/html/internal.h"
30 /* #define DEBUG_CSS */
33 /* TODO: A way to disable CSS completely, PLUS a way to stop various property
34 * groups from taking effect. (Ie. way to turn out effect of 'display: none'
35 * or aligning or colors but keeping all the others.) --pasky */
38 typedef void (*css_applier_T)(struct html_context *html_context,
39 struct html_element *element,
40 struct css_property *prop);
42 static void
43 css_apply_color(struct html_context *html_context, struct html_element *element,
44 struct css_property *prop)
46 assert(prop->value_type == CSS_VT_COLOR);
48 if (use_document_fg_colors(html_context->options))
49 element->attr.style.fg = prop->value.color;
52 static void
53 css_apply_background_color(struct html_context *html_context,
54 struct html_element *element,
55 struct css_property *prop)
57 assert(prop->value_type == CSS_VT_COLOR);
59 if (use_document_bg_colors(html_context->options))
60 element->attr.style.bg = prop->value.color;
63 static void
64 css_apply_display(struct html_context *html_context, struct html_element *element,
65 struct css_property *prop)
67 assert(prop->value_type == CSS_VT_DISPLAY);
69 switch (prop->value.display) {
70 case CSS_DISP_INLINE:
71 element->linebreak = 0;
72 break;
73 case CSS_DISP_BLOCK:
74 /* 1 or 2, that is the question. I went for 2 since it
75 * gives a more "blocky" feel and it's more common.
76 * YMMV. */
77 element->linebreak = 2;
78 break;
79 default:
80 INTERNAL("Bad prop->value.display %d", prop->value.display);
81 break;
85 static void
86 css_apply_font_attribute(struct html_context *html_context,
87 struct html_element *element, struct css_property *prop)
89 assert(prop->value_type == CSS_VT_FONT_ATTRIBUTE);
90 element->attr.style.attr |= prop->value.font_attribute.add;
91 element->attr.style.attr &= ~prop->value.font_attribute.rem;
94 /* FIXME: Because the current CSS doesn't provide reasonable defaults for each
95 * HTML element this applier will cause bad rendering of <pre> tags. */
96 static void
97 css_apply_text_align(struct html_context *html_context,
98 struct html_element *element, struct css_property *prop)
100 assert(prop->value_type == CSS_VT_TEXT_ALIGN);
101 element->parattr.align = prop->value.text_align;
104 /* XXX: Sort like the css_property_type */
105 static css_applier_T css_appliers[CSS_PT_LAST] = {
106 /* CSS_PT_NONE */ NULL,
107 /* CSS_PT_BACKGROUND */ css_apply_background_color,
108 /* CSS_PT_BACKGROUND_COLOR */ css_apply_background_color,
109 /* CSS_PT_COLOR */ css_apply_color,
110 /* CSS_PT_DISPLAY */ css_apply_display,
111 /* CSS_PT_FONT_STYLE */ css_apply_font_attribute,
112 /* CSS_PT_FONT_WEIGHT */ css_apply_font_attribute,
113 /* CSS_PT_TEXT_ALIGN */ css_apply_text_align,
114 /* CSS_PT_TEXT_DECORATION */ css_apply_font_attribute,
115 /* CSS_PT_WHITE_SPACE */ css_apply_font_attribute,
118 /* This looks for a match in list of selectors. */
119 static void
120 examine_element(struct html_context *html_context, struct css_selector *base,
121 enum css_selector_type seltype, enum css_selector_relation rel,
122 struct list_head *selectors, struct html_element *element,
123 struct list_head *html_stack)
125 struct css_selector *selector;
126 unsigned char *code;
128 #ifdef DEBUG_CSS
129 DBG("examine_element(%p, %s, %d, %d, %p, %.*s);", html_context, base->name, seltype, rel, selectors, element->namelen, element->name);
130 #define dbginfo(sel, type_, base) \
131 DBG("Matched selector %s (rel %d type %d [m%d])! Children %p !!%d, props !!%d", sel->name, sel->relation, sel->type, sel->type == type_, &sel->leaves, !list_empty(sel->leaves), !list_empty(sel->properties))
132 #else
133 #define dbginfo(sel, type, base)
134 #endif
136 #define process_found_selector(sel, type, base) \
137 if (selector) { \
138 dbginfo(sel, type, base); \
139 merge_css_selectors(base, sel); \
140 /* Ancestor matches? */ \
141 if ((struct list_head *) element->next != html_stack) { \
142 struct html_element *ancestor; \
143 /* This is less effective than doing reverse iterations,
144 * first over sel->leaves and then over the HTML stack,
145 * which shines in the most common case where there are
146 * no CSR_ANCESTOR selector leaves. However we would
147 * have to duplicate the whole examine_element(), so if
148 * profiles won't show it really costs... */ \
149 for (ancestor = element->next; \
150 (struct list_head *) ancestor != html_stack;\
151 ancestor = ancestor->next) \
152 examine_element(html_context, base, \
153 CST_ELEMENT, CSR_ANCESTOR, \
154 &sel->leaves, ancestor, \
155 html_stack); \
156 examine_element(html_context, base, \
157 CST_ELEMENT, CSR_PARENT, \
158 &sel->leaves, element->next, \
159 html_stack); \
161 /* More specific matches? */ \
162 examine_element(html_context, base, type + 1, \
163 CSR_SPECIFITY, \
164 &sel->leaves, element, html_stack); \
167 if (seltype <= CST_ELEMENT && element->namelen) {
168 selector = find_css_selector(selectors, CST_ELEMENT, rel,
169 "*", 1);
170 process_found_selector(selector, CST_ELEMENT, base);
172 selector = find_css_selector(selectors, CST_ELEMENT, rel,
173 element->name, element->namelen);
174 process_found_selector(selector, CST_ELEMENT, base);
177 if (!element->options)
178 return;
180 /* TODO: More pseudo-classess. --pasky */
181 if (element->pseudo_class & ELEMENT_LINK) {
182 selector = find_css_selector(selectors, CST_PSEUDO, rel, "link", -1);
183 process_found_selector(selector, CST_PSEUDO, base);
185 if (element->pseudo_class & ELEMENT_VISITED) {
186 selector = find_css_selector(selectors, CST_PSEUDO, rel, "visited", -1);
187 process_found_selector(selector, CST_PSEUDO, base);
190 code = get_attr_val(element->options, "class", html_context->options);
191 if (code && seltype <= CST_CLASS) {
192 unsigned char *class = code;
194 while (class) {
195 unsigned char *end = strchr(class, ' ');
197 if (end)
198 *end++ = 0;
200 selector = find_css_selector(selectors, CST_CLASS, rel, class, -1);
201 process_found_selector(selector, CST_CLASS, base);
202 class = end;
205 mem_free_if(code);
207 code = get_attr_val(element->options, "id", html_context->options);
208 if (code && seltype <= CST_ID) {
209 selector = find_css_selector(selectors, CST_ID, rel, code, -1);
210 process_found_selector(selector, CST_ID, base);
212 if (code) mem_free(code);
214 #undef process_found_selector
215 #undef dbginfo
218 struct css_selector *
219 get_css_selector_for_element(struct html_context *html_context,
220 struct html_element *element,
221 struct css_stylesheet *css,
222 struct list_head *html_stack)
224 unsigned char *code;
225 struct css_selector *selector;
227 assert(element && element->options && css);
229 selector = init_css_selector(NULL, CST_ELEMENT, NULL, 0);
230 if (!selector)
231 return NULL;
233 #ifdef DEBUG_CSS
234 DBG("Applying to element %.*s...", element->namelen, element->name);
235 #endif
237 examine_element(html_context, selector, CST_ELEMENT, CSR_ROOT,
238 &css->selectors, element, html_stack);
240 #ifdef DEBUG_CSS
241 DBG("Element %.*s applied.", element->namelen, element->name);
242 #endif
244 code = get_attr_val(element->options, "style", html_context->options);
245 if (code) {
246 struct css_selector *stylesel;
247 struct scanner scanner;
249 stylesel = init_css_selector(NULL, CST_ELEMENT, NULL, 0);
251 if (stylesel) {
252 init_scanner(&scanner, &css_scanner_info, code, NULL);
253 css_parse_properties(&stylesel->properties, &scanner);
254 merge_css_selectors(selector, stylesel);
255 done_css_selector(stylesel);
257 mem_free(code);
260 return selector;
263 void
264 apply_css_selector_style(struct html_context *html_context,
265 struct html_element *element,
266 struct css_selector *selector)
268 struct css_property *property;
270 foreach (property, selector->properties) {
271 assert(property->type < CSS_PT_LAST);
272 /* We don't assert general prop->value_type here because I
273 * don't want hinder properties' ability to potentially make
274 * use of multiple value types. */
275 assert(css_appliers[property->type]);
276 css_appliers[property->type](html_context, element, property);
280 void
281 css_apply(struct html_context *html_context, struct html_element *element,
282 struct css_stylesheet *css, struct list_head *html_stack)
284 struct css_selector *selector;
286 selector = get_css_selector_for_element(html_context, element, css,
287 html_stack);
288 if (!selector) return;
290 apply_css_selector_style(html_context, element, selector);
292 done_css_selector(selector);