1use super::css;
3use super::dom;
5use super::dom::Node;
6use std::collections::HashMap;
7
8pub type CssPropertyMap = HashMap<String, css::Value>;
9
10#[derive(PartialEq)]
11pub enum Display {
12 Inline,
13 Block,
14 None,
15}
16
17pub struct StyledNode<'a> {
18 pub node: &'a Node,
19 pub css_specified_values: CssPropertyMap,
20 pub children: Vec<StyledNode<'a>>,
21}
22
23impl<'a> StyledNode<'a> {
24 pub fn value(&'a self, name: &str) -> Option<&'a css::Value> {
25 self.css_specified_values.get(name)
26 }
27
28 pub fn lookup(
29 &'a self,
30 name: &str,
31 fallback_name: &str,
32 default: &'a css::Value,
33 ) -> &'a css::Value {
34 self.value(name)
35 .unwrap_or_else(|| self.value(fallback_name).unwrap_or_else(|| default))
36 }
37
38 pub fn display(&self) -> Display {
39 match self.value("display") {
40 Some(css::Value::Keyword(s)) => match s.as_str() {
41 "block" => Display::Block,
42 "none" => Display::None,
43 _ => Display::Inline,
44 },
45 _ => Display::Inline,
46 }
47 }
48}
49
50pub fn style_tree<'a>(root: &'a Node, stylesheet: &'a css::Stylesheet) -> StyledNode<'a> {
51 StyledNode {
52 node: root,
53 css_specified_values: match root {
54 Node::Element(data) => css_specified_values(data, stylesheet),
55 Node::Text(_) => HashMap::new(),
56 },
57 children: root
58 .children()
59 .iter()
60 .map(|child| style_tree(child, stylesheet))
61 .collect(),
62 }
63}
64
65fn css_specified_values(elem: &dom::ElementData, stylesheet: &css::Stylesheet) -> CssPropertyMap {
66 let mut values = HashMap::new();
67 let mut rules = matching_rules(elem, stylesheet);
68
69 rules.sort_by(|&(a, _), &(b, _)| a.cmp(&b));
71 for (_, rule) in rules {
72 for declaration in &rule.declarations {
73 values.insert(declaration.name.clone(), declaration.value.clone());
74 }
75 }
76 values
77}
78
79type MatchedRule<'a> = (css::Specifity, &'a css::Rule);
80
81fn matching_rules<'a>(
82 elem: &'a dom::ElementData,
83 stylesheet: &'a css::Stylesheet,
84) -> Vec<MatchedRule<'a>> {
85 stylesheet
86 .rules
87 .iter()
88 .filter_map(|rule| match_rule(elem, rule))
89 .collect()
90}
91
92fn match_rule<'a>(elem: &dom::ElementData, rule: &'a css::Rule) -> Option<MatchedRule<'a>> {
93 match_selectors(elem, &rule.selectors).map(|selector| (selector.specifity(), rule))
94}
95
96fn match_selectors<'a>(
97 elem: &dom::ElementData,
98 sorted_selectors: &'a css::SortedSelectors,
99) -> Option<&'a css::Selector> {
100 sorted_selectors
102 .selectors
103 .iter()
104 .find(|selector| matches(elem, *selector))
105}
106
107fn matches(elem: &dom::ElementData, selector: &css::Selector) -> bool {
108 match selector {
109 css::Selector::Simple(simple_selector) => matches_simple_selector(elem, simple_selector),
110 }
111}
112
113fn matches_simple_selector(elem: &dom::ElementData, selector: &css::SimpleSelector) -> bool {
114 if !selector.tag_name.iter().all(|name| elem.tag_name == *name) {
116 return false;
117 }
118
119 if !selector.id.iter().all(|id| elem.id() == Some(id)) {
121 return false;
122 }
123
124 let elem_classes = elem.classes();
126 if !selector
127 .classes
128 .iter()
129 .all(|class| elem_classes.contains(&**class))
130 {
131 return false;
132 }
133
134 true
136}
137
138#[cfg(test)]
139mod test {
140 use super::*;
141 use crate::css;
142 use crate::dom;
143 use maplit::*;
144
145 #[test]
146 fn simple_selector_match_test() {
147 let div_selector = css::SimpleSelector {
148 tag_name: Some("div".to_string()),
149 ..Default::default()
150 };
151
152 let div_elem = dom::ElementData {
153 tag_name: "div".to_string(),
154 ..Default::default()
155 };
156
157 let p_elem = dom::ElementData {
158 tag_name: "p".to_string(),
159 ..Default::default()
160 };
161
162 assert!(matches_simple_selector(&div_elem, &div_selector));
163 assert!(!matches_simple_selector(&p_elem, &div_selector));
164
165 let class_foo_selector = css::SimpleSelector {
166 classes: btreeset! { "foo".to_string() },
167 ..Default::default()
168 };
169
170 assert!(!matches_simple_selector(&div_elem, &class_foo_selector));
171 assert!(!matches_simple_selector(&p_elem, &class_foo_selector));
172
173 let div_class_foo_elem = dom::ElementData {
174 tag_name: "div".to_string(),
175 attrs: btreemap! {
176 "class".to_string() => "foo".to_string()
177 },
178 ..Default::default()
179 };
180
181 let div_class_bar_elem = dom::ElementData {
182 tag_name: "div".to_string(),
183 attrs: btreemap! {
184 "class".to_string() => "bar".to_string()
185 },
186 ..Default::default()
187 };
188
189 let div_class_foo_bar_elem = dom::ElementData {
190 tag_name: "div".to_string(),
191 attrs: btreemap! {
192 "class".to_string() => "foo bar".to_string()
193 },
194 ..Default::default()
195 };
196
197 assert!(matches_simple_selector(
198 &div_class_foo_elem,
199 &class_foo_selector
200 ));
201
202 assert!(!matches_simple_selector(
203 &div_class_bar_elem,
204 &class_foo_selector
205 ));
206
207 assert!(matches_simple_selector(
208 &div_class_foo_bar_elem,
209 &class_foo_selector
210 ));
211
212 let universal_selector: css::SimpleSelector = Default::default();
213 assert!(matches_simple_selector(&div_elem, &universal_selector));
214
215 let div_id_foo_elem = dom::ElementData {
216 tag_name: "div".to_string(),
217 attrs: btreemap! {
218 "id".to_string() => "foo".to_string()
219 },
220 ..Default::default()
221 };
222
223 assert!(matches_simple_selector(
224 &div_id_foo_elem,
225 &css::SimpleSelector {
226 id: Some("foo".to_string()),
227 ..Default::default()
228 }
229 ));
230
231 assert!(!matches_simple_selector(
232 &div_id_foo_elem,
233 &css::SimpleSelector {
234 id: Some("xxx".to_string()),
235 ..Default::default()
236 }
237 ));
238
239 let div_id_foo_class1_class2_elem = dom::ElementData {
240 tag_name: "div".to_string(),
241 attrs: btreemap! {
242 "id".to_string() => "foo".to_string(),
243 "class".to_string() => "class1 class2".to_string(),
244 },
245 ..Default::default()
246 };
247
248 assert!(matches_simple_selector(
249 &div_id_foo_class1_class2_elem,
250 &css::SimpleSelector {
251 id: Some("foo".to_string()),
252 classes: btreeset! {"class1".to_string()},
253 ..Default::default()
254 }
255 ));
256
257 assert!(!matches_simple_selector(
258 &div_id_foo_class1_class2_elem,
259 &css::SimpleSelector {
260 id: Some("foo".to_string()),
261 classes: btreeset! {"class1 classxx".to_string()},
262 ..Default::default()
263 }
264 ));
265 }
266
267 #[test]
268 fn match_selectors_test() {
269 let div = dom::ElementData {
270 tag_name: "div".to_string(),
271 ..Default::default()
272 };
273
274 assert!(match_selectors(&div, &css::SortedSelectors::new(vec![])).is_none());
275 assert!(match_selectors(
276 &div,
277 &css::SortedSelectors::new(vec![css::Selector::id("XXX")]),
278 )
279 .is_none());
280
281 assert_eq!(
282 match_selectors(
283 &div,
284 &css::SortedSelectors::new(vec![css::Selector::universal()]),
285 ),
286 Some(&css::Selector::universal())
287 );
288
289 let elem = dom::ElementData {
290 tag_name: "div".to_string(),
291 attrs: btreemap! {
292 "id".to_string() => "foo".to_string(),
293 "class".to_string() => "class1 class2".to_string(),
294 },
295 ..Default::default()
296 };
297
298 assert_eq!(
299 match_selectors(
300 &elem,
301 &css::SortedSelectors::new(vec![
302 css::Selector::tag("div"),
303 css::Selector::class(&["class1"]),
304 css::Selector::id("foo"),
305 ])
306 ),
307 Some(&css::Selector::id("foo")),
308 "id should win"
309 );
310
311 assert_eq!(
312 match_selectors(
313 &elem,
314 &css::SortedSelectors::new(vec![
315 css::Selector::tag("div"),
316 css::Selector::class(&["class1"]),
317 ])
318 ),
319 Some(&css::Selector::class(&["class1"])),
320 "class should win"
321 );
322
323 assert_eq!(
324 match_selectors(
325 &elem,
326 &css::SortedSelectors::new(vec![
327 css::Selector::class(&["class1"]),
328 css::Selector::class(&["class1", "class2"]),
329 css::Selector::class(&["class2"]),
330 ])
331 ),
332 Some(&css::Selector::class(&["class1", "class2"])),
333 "More classes should win"
334 );
335 }
336
337 #[test]
338 fn matching_rules_test() {
339 let stylesheet = css::Stylesheet {
340 rules: vec![
341 css::Rule {
342 selectors: css::SortedSelectors::new(vec![css::Selector::tag("div")]),
343 declarations: vec![css::Declaration::color((0, 0, 0))],
344 },
345 css::Rule {
346 selectors: css::SortedSelectors::new(vec![css::Selector::tag("foo")]),
347 declarations: vec![css::Declaration::color((1, 1, 1))],
348 },
349 css::Rule {
350 selectors: css::SortedSelectors::new(vec![css::Selector::tag("div")]),
351 declarations: vec![css::Declaration::color((2, 2, 2))],
352 },
353 ],
354 };
355
356 let div = dom::ElementData {
357 tag_name: "div".to_string(),
358 ..Default::default()
359 };
360
361 let matched_declarations = matching_rules(&div, &stylesheet)
362 .into_iter()
363 .map(|(_speficity, rule)| &rule.declarations)
364 .collect::<Vec<_>>();
365
366 assert_eq!(
367 matched_declarations,
368 vec![
369 &vec![css::Declaration::color((0, 0, 0))],
370 &vec![css::Declaration::color((2, 2, 2))],
371 ]
372 );
373 }
374
375 #[test]
376 fn css_specified_values_test() {
377 let stylesheet = css::Stylesheet {
378 rules: vec![
379 css::Rule {
380 selectors: css::SortedSelectors::new(vec![css::Selector::tag("div")]),
381 declarations: vec![css::Declaration::color((0, 0, 0))],
382 },
383 css::Rule {
384 selectors: css::SortedSelectors::new(vec![css::Selector::id("foo")]),
385 declarations: vec![css::Declaration::color((1, 1, 1))],
386 },
387 css::Rule {
388 selectors: css::SortedSelectors::new(vec![css::Selector::id("foo")]),
389 declarations: vec![css::Declaration::color((2, 2, 2))],
390 },
391 css::Rule {
392 selectors: css::SortedSelectors::new(vec![css::Selector::tag("div")]),
393 declarations: vec![css::Declaration::color((3, 3, 3))],
394 },
395 ],
396 };
397
398 let div = dom::ElementData {
399 tag_name: "div".to_string(),
400 attrs: btreemap! {
401 "id".to_string() => "foo".to_string()
402 },
403 ..Default::default()
404 };
405
406 let values = css_specified_values(&div, &stylesheet);
407 assert_eq!(
408 values,
409 hashmap! { "color".to_string() => css::Value::color((2, 2, 2)) }
410 );
411 }
412}