1
use std::collections::HashMap;
2

            
3
use crate::css::{Rule, Selector, Sheet, Specificity, Value};
4
use crate::dom::Node;
5

            
6
pub type PropertyMap = HashMap<String, Value>;
7

            
8
#[derive(Debug)]
9
pub struct StyledNode<'a> {
10
    pub node: &'a Node,
11
    pub specified_values: PropertyMap,
12
    pub children: Vec<StyledNode<'a>>,
13
}
14

            
15
impl<'a> From<&'a StyledNode<'a>> for String {
16
6
    fn from(styled_node: &StyledNode) -> String {
17
6
        let mut output = String::new();
18
6

            
19
6
        match styled_node.node {
20
4
            Node::Element { tag, attrs, .. } => {
21
4
                output.push('<');
22
4
                output.push_str(tag);
23
4

            
24
4
                let attrs_str = attrs.iter().fold("".to_owned(), |acc, x| {
25
1
                    format!("{} {}=\"{}\"", acc, x.0, x.1)
26
4
                });
27
4

            
28
4
                output.push_str(&attrs_str);
29
4

            
30
4
                output.push_str(" style=\"");
31
4

            
32
4
                let mut specified_values: Vec<_> = styled_node.specified_values.iter().collect();
33
4
                specified_values.sort_by(|&(a, _), &(b, _)| a.cmp(b));
34

            
35
6
                for (key, value) in specified_values {
36
2
                    output.push_str(&format!("{}:{};", key, String::from(value)));
37
2
                }
38
4
                output.push('"');
39
4

            
40
4
                output.push('>');
41

            
42
9
                for child in &styled_node.children {
43
5
                    output.push_str(&String::from(child));
44
5
                }
45

            
46
4
                output.push_str("</");
47
4
                output.push_str(tag);
48
4
                output.push('>');
49
            }
50
2
            Node::Text(t) => {
51
2
                output.push_str(t);
52
2
            }
53
        }
54

            
55
6
        output
56
6
    }
57
}
58

            
59
#[derive(PartialEq)]
60
pub enum Display {
61
    Inline,
62
    Block,
63
    None,
64
}
65

            
66
impl<'a> StyledNode<'a> {
67
214
    pub fn value(&self, name: &str) -> Option<Value> {
68
214
        self.specified_values.get(name).cloned()
69
214
    }
70

            
71
84
    pub fn lookup(&self, name: &str, fallback_name: &str, default: &Value) -> Value {
72
84
        self.value(name)
73
84
            .unwrap_or_else(|| self.value(fallback_name).unwrap_or_else(|| default.clone()))
74
84
    }
75

            
76
    pub fn display(&self) -> Display {
77
32
        match self.value("display") {
78
20
            Some(Value::Keyword(s)) => match &*s {
79
20
                "block" => Display::Block,
80
8
                "none" => Display::None,
81
8
                _ => Display::Inline,
82
            },
83
12
            _ => Display::Inline,
84
        }
85
32
    }
86
}
87

            
88
32
pub fn style_tree<'a>(root: &'a Node, sheet: &'a Sheet) -> StyledNode<'a> {
89
32
    match root {
90
21
        Node::Element { children, .. } => StyledNode {
91
21
            node: root,
92
21
            specified_values: get_specified_values(root, sheet),
93
21
            children: children
94
21
                .iter()
95
28
                .map(|child| style_tree(child, sheet))
96
21
                .collect(),
97
21
        },
98
11
        Node::Text(_) => StyledNode {
99
11
            node: root,
100
11
            specified_values: HashMap::new(),
101
11
            children: vec![],
102
11
        },
103
    }
104
32
}
105

            
106
21
fn get_specified_values(node: &Node, sheet: &Sheet) -> PropertyMap {
107
21
    let mut values = HashMap::new();
108
21
    let mut rules = matching_rules(node, sheet);
109
21

            
110
21
    rules.sort_by(|&(a, _), &(b, _)| a.cmp(&b));
111
39
    for (_, rule) in rules {
112
61
        for declaration in &rule.declarations {
113
43
            values.insert(declaration.name.clone(), declaration.value.clone());
114
43
        }
115
    }
116
21
    values
117
21
}
118

            
119
type MatchedRule<'a> = (Specificity, &'a Rule);
120

            
121
21
fn matching_rules<'a>(node: &Node, sheet: &'a Sheet) -> Vec<MatchedRule<'a>> {
122
21
    sheet
123
21
        .0
124
21
        .iter()
125
50
        .filter_map(|rule| match_rule(node, rule))
126
21
        .collect()
127
21
}
128

            
129
50
fn match_rule<'a>(node: &Node, rule: &'a Rule) -> Option<MatchedRule<'a>> {
130
50
    rule.selectors
131
50
        .iter()
132
76
        .find(|selector| matches(node, selector))
133
50
        .map(|selector| (selector.get_specificity(), rule))
134
50
}
135

            
136
76
fn matches(node: &Node, selector: &Selector) -> bool {
137
76
    match node {
138
        Node::Element {
139
76
            tag,
140
76
            attrs: _,
141
76
            children: _,
142
76
        } => {
143
76
            if selector.tag.iter().any(|name| *tag != *name) {
144
55
                return false;
145
21
            }
146
21

            
147
21
            if selector
148
21
                .id
149
21
                .iter()
150
21
                .any(|id| node.get_id().unwrap_or("") != id)
151
            {
152
                return false;
153
21
            }
154
21

            
155
21
            let node_classes = node.get_classes();
156
21
            if selector
157
21
                .class
158
21
                .iter()
159
21
                .any(|class| !node_classes.contains(&**class))
160
            {
161
3
                return false;
162
18
            }
163
18

            
164
18
            // TODO: match attrs
165
18

            
166
18
            // Only matching selector components
167
18
            true
168
        }
169
        Node::Text(_) => false,
170
    }
171
76
}
172

            
173
#[cfg(test)]
174
mod tests {
175
    use crate::css::*;
176
    use crate::dom::*;
177
    use crate::style::*;
178

            
179
1
    #[test]
180
1
    fn test_styled_node() {
181
1
        let document = elem("html").add_attr("lang", "NL").inner_html(
182
1
            r#"
183
1
            <head>
184
1
                <title>Hello, world!</title>
185
1
            </head>
186
1
            <body class="bar">
187
1
                <h1>Hi!</h1>
188
1
                <p>Bye!</p>
189
1
            </body>"#,
190
1
        );
191
1

            
192
1
        let style = sheet().add_rule(
193
1
            rule()
194
1
                .add_selector(selector().add_tag("body").add_class("foo"))
195
1
                .add_selector(selector().add_tag("p"))
196
1
                .add_declaration("margin", Value::Keyword("auto".to_owned()))
197
1
                .add_declaration("width", Value::Length(24.0, Unit::Px)),
198
1
        );
199
1

            
200
1
        let actual = style_tree(&document, &style);
201
1

            
202
1
        let expected = HashMap::from([
203
1
            ("margin".to_owned(), Value::Keyword("auto".to_owned())),
204
1
            ("width".to_owned(), Value::Length(24.0, Unit::Px)),
205
1
        ]);
206
1

            
207
1
        // element p matches selector p
208
1
        assert_eq!(actual.children[1].children[1].specified_values, expected);
209

            
210
        // element class bar does not match selector class foo
211
1
        assert_eq!(actual.children[1].specified_values, HashMap::new());
212
1
    }
213

            
214
1
    #[test]
215
1
    fn test_to_str() {
216
1
        let document = elem("html").inner_html(
217
1
            r#"
218
1
            <body class="bar">
219
1
                <h1>Hi!</h1>
220
1
                <p>Bye!</p>
221
1
            </body>"#,
222
1
        );
223
1

            
224
1
        let style = sheet().add_rule(
225
1
            rule()
226
1
                .add_selector(selector().add_tag("body").add_class("foo"))
227
1
                .add_selector(selector().add_tag("p"))
228
1
                .add_declaration("margin", Value::Keyword("auto".to_owned()))
229
1
                .add_declaration("width", Value::Length(24.0, Unit::Px)),
230
1
        );
231
1

            
232
1
        let actual = style_tree(&document, &style);
233
1
        let expected = r#"<html style=""><body class="bar" style=""><h1 style="">Hi!</h1><p style="margin:auto;width:24px;">Bye!</p></body></html>"#;
234
1
        assert_eq!(String::from(&actual), expected);
235
1
    }
236
}