1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
mod compound;
mod simple;

use crate::Element;

use self::{compound::CompoundSelector, simple::SimpleSelector};

/// Basic selector. It follows the
/// [CSS selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors)
/// standard, but not all rules are supported now. Please refer
/// to [`Selector::from`](Selector::from).
#[derive(Debug)]
pub struct Selector(Vec<CompoundSelector>);

impl Selector {
    /// Check if the `element` matches the `selector`.
    ///
    /// ```
    /// use html_editor::{Node, Element};
    /// use html_editor::operation::*;
    ///
    /// let element: Element = Element::new(
    ///     "div",
    ///     vec![("id", "app")],
    ///     vec![Node::Text("Hello World!".to_string())],
    /// );
    ///
    /// let selector = Selector::from("div#app");
    ///
    /// assert_eq!(selector.matches(&element), true);
    /// ```
    pub fn matches(&self, element: &Element) -> bool {
        let element_classes = element
            .attrs
            .iter()
            .find(|(key, _)| key == "class")
            .map(|(_, v)| v.split(' ').map(|name| name.trim()).collect::<Vec<_>>());
        let element_id = element
            .attrs
            .iter()
            .find(|(key, _)| key == "id")
            .map(|(_, v)| v);

        self.0.iter().any(|compound_selector| {
            compound_selector
                .0
                .iter()
                .all(|simple_selector| match simple_selector {
                    SimpleSelector::Class(selector_class) => match &element_classes {
                        Some(element_classes) => element_classes
                            .iter()
                            .any(|element_class| element_class == selector_class),
                        None => false,
                    },
                    SimpleSelector::Id(selector_id) => match element_id {
                        Some(element_id) => element_id == selector_id,
                        None => false,
                    },
                    SimpleSelector::Tag(tag) => tag == &element.name,
                })
        })
    }
}

impl From<&str> for Selector {
    /// Generate a selector from given string, following the CSS
    /// selector standard.
    ///
    /// Not all rules are supported. Below shows the rules currently
    /// supported:
    ///
    /// ```
    /// use html_editor::operation::Selector;
    ///
    /// // Type Selector
    /// Selector::from("span");
    /// // Class selector
    /// Selector::from(".class");
    /// // ID selector
    /// Selector::from("#id");
    ///
    /// // Selector list
    /// Selector::from("h1, h2");
    /// // Compound selector
    /// Selector::from("input.username");
    ///
    /// // Disallowed input that may cause unexpected result
    /// Selector::from("div span");
    /// Selector::from("a[target=_blank]");
    /// ```
    fn from(selector: &str) -> Self {
        Selector(selector.split(',').map(CompoundSelector::from).collect())
    }
}