1
use std::collections::HashSet;
2

            
3
use crate::html::Parser;
4

            
5
#[derive(Debug)]
6
pub enum Node {
7
    Element {
8
        tag: String,
9
        attrs: Vec<(String, String)>,
10
        children: Vec<Node>,
11
    },
12
    Text(String),
13
}
14

            
15
impl Node {
16
45
    pub fn elem(tag: &str) -> Self {
17
45
        Node::Element {
18
45
            tag: tag.to_owned(),
19
45
            attrs: vec![],
20
45
            children: vec![],
21
45
        }
22
45
    }
23

            
24
22
    pub fn text(t: &str) -> Self {
25
22
        Node::Text(t.to_owned())
26
22
    }
27

            
28
6
    pub fn add_text(self, t: &str) -> Self {
29
6
        self.add_child(text(t))
30
6
    }
31

            
32
    pub fn add_child(mut self, c: Self) -> Self {
33
        if let Node::Element {
34
16
            ref mut children, ..
35
16
        } = self
36
16
        {
37
16
            children.push(c);
38
16
        }
39
16
        self
40
16
    }
41

            
42
    pub fn add_children(mut self, cs: Vec<Self>) -> Self {
43
        if let Node::Element {
44
28
            ref mut children, ..
45
28
        } = self
46
        {
47
64
            for item in cs {
48
36
                children.push(item)
49
            }
50
        }
51
28
        self
52
28
    }
53

            
54
    pub fn add_attr(mut self, key: &str, value: &str) -> Self {
55
5
        if let Node::Element { ref mut attrs, .. } = self {
56
5
            attrs.push((key.to_owned(), value.to_owned()));
57
5
        }
58
5
        self
59
5
    }
60

            
61
    pub fn add_attrs(mut self, kvs: Vec<(String, String)>) -> Self {
62
28
        if let Node::Element { ref mut attrs, .. } = self {
63
32
            for item in kvs {
64
4
                attrs.push(item)
65
            }
66
        }
67
28
        self
68
28
    }
69

            
70
    pub fn inner_html(mut self, html: &str) -> Self {
71
        if let Node::Element {
72
3
            ref mut children, ..
73
3
        } = self
74
3
        {
75
3
            children.clear();
76
3
            children.append(&mut Parser::parse_no_root(html.to_owned()));
77
3
        }
78
3
        self
79
3
    }
80

            
81
    pub fn get_id(&self) -> Option<&str> {
82
1
        if let Node::Element { ref attrs, .. } = self {
83
1
            for attr in attrs {
84
1
                if attr.0 == "id" {
85
1
                    return Some(&attr.1);
86
                }
87
            }
88
        }
89

            
90
        None
91
1
    }
92

            
93
    pub fn get_classes(&self) -> HashSet<&str> {
94
22
        if let Node::Element { ref attrs, .. } = self {
95
22
            for attr in attrs {
96
5
                if attr.0 == "class" {
97
5
                    return attr.1.split(' ').collect();
98
                }
99
            }
100
        }
101

            
102
17
        HashSet::new()
103
22
    }
104

            
105
    pub fn get_text_content(&self) -> String {
106
        match self {
107
            Node::Element { ref children, .. } => {
108
                let mut content = "".to_owned();
109
                for c in children {
110
                    content.push_str(&c.get_text_content());
111
                }
112
                content
113
            }
114
            Node::Text(t) => t.to_owned(),
115
        }
116
    }
117

            
118
    pub fn get_elements_by_tag_name(&self, tag_name: &str) -> Vec<&Self> {
119
        match self {
120
            Node::Element {
121
                ref tag,
122
                ref children,
123
                ..
124
            } => {
125
                let mut result = vec![];
126

            
127
                if tag == tag_name {
128
                    result.push(self);
129
                }
130

            
131
                for child in children {
132
                    result.append(&mut child.get_elements_by_tag_name(tag_name));
133
                }
134

            
135
                result
136
            }
137
            Node::Text(_) => vec![],
138
        }
139
    }
140
}
141

            
142
impl PartialEq for Node {
143
12
    fn eq(&self, other: &Self) -> bool {
144
12
        match self {
145
            Node::Element {
146
8
                tag,
147
8
                attrs,
148
8
                children,
149
8
            } => match other {
150
                Node::Element {
151
8
                    tag: other_tag,
152
8
                    attrs: other_attrs,
153
8
                    children: other_children,
154
8
                } => tag == other_tag && attrs == other_attrs && children == other_children,
155
                _ => false,
156
            },
157
4
            Node::Text(t) => {
158
4
                if let Node::Text(other_t) = other {
159
4
                    t == other_t
160
                } else {
161
                    false
162
                }
163
            }
164
        }
165
12
    }
166
}
167

            
168
impl From<&Node> for String {
169
9
    fn from(n: &Node) -> String {
170
9
        match n {
171
            Node::Element {
172
6
                tag,
173
6
                attrs,
174
6
                children,
175
6
            } => {
176
6
                let attrs_str = attrs.iter().fold("".to_owned(), |acc, x| {
177
1
                    format!("{} {}=\"{}\"", acc, x.0, x.1)
178
6
                });
179
8
                let children_str = children.iter().fold("".to_owned(), |acc, x| {
180
8
                    format!("{}{}", acc, String::from(x))
181
8
                });
182
6
                format!("<{}{}>{}</{}>", &tag, attrs_str, children_str, &tag)
183
            }
184
3
            Node::Text(t) => String::from(t),
185
        }
186
9
    }
187
}
188

            
189
45
pub fn elem(tag: &str) -> Node {
190
45
    Node::elem(tag)
191
45
}
192

            
193
22
pub fn text(t: &str) -> Node {
194
22
    Node::text(t)
195
22
}
196

            
197
#[cfg(test)]
198
mod tests {
199
    use crate::dom::{elem, Node};
200

            
201
1
    #[test]
202
1
    fn test_to_string() {
203
1
        let actual = elem("html")
204
1
            .add_attr("lang", "NL")
205
1
            .add_child(elem("head").add_child(elem("title").add_text("Hello, world!")))
206
1
            .add_child(
207
1
                elem("body")
208
1
                    .add_child(elem("h1").add_text("Hi!"))
209
1
                    .add_child(elem("p").add_text("Bye!")),
210
1
            );
211
1
        let expected = "\
212
1
            <html lang=\"NL\">\
213
1
                <head>\
214
1
                    <title>Hello, world!</title>\
215
1
                </head>\
216
1
                <body>\
217
1
                    <h1>Hi!</h1>\
218
1
                    <p>Bye!</p>\
219
1
                </body>\
220
1
            </html>\
221
1
        ";
222
1
        assert_eq!(String::from(&actual), expected);
223
1
    }
224

            
225
1
    #[test]
226
1
    fn test_inner_html() {
227
1
        let actual = elem("html").inner_html("<h1>hello</h1>");
228
1
        let expected = "<html><h1>hello</h1></html>";
229
1
        assert_eq!(actual, Node::from(expected));
230
1
    }
231

            
232
1
    #[test]
233
1
    fn test_get_id() {
234
1
        let doc = elem("html").add_attr("id", "foo");
235
1
        assert_eq!(doc.get_id().unwrap(), "foo");
236
1
    }
237

            
238
1
    #[test]
239
1
    fn test_get_classes() {
240
1
        let doc = elem("html").add_attr("class", "foo bar");
241
1
        let classes = doc.get_classes();
242
1
        assert!(classes.contains("foo"));
243
1
        assert!(classes.contains("bar"));
244
1
    }
245
}