crml_core/
selector.rs

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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/// The extracted data from the given [`Selector].
#[derive(Clone, Debug)]
pub struct SelectorState {
    pub tag: String,
    pub classes: Option<Vec<String>>,
    pub id: Option<String>,
    pub attributes: Option<Vec<String>>,
}

impl SelectorState {
    /// Try to save the current buffer to the state (in the correct field).
    pub fn try_save(&mut self, mode: ParserMode, mut buffer: String) -> String {
        match mode {
            ParserMode::None => {
                if self.tag.is_empty() {
                    // tag cannot be overwritten
                    self.tag = buffer;
                    buffer = String::new();
                }
            }
            ParserMode::Class => {
                if self.classes.is_none() {
                    // classes is none; init classes with Some(vec![buffer])
                    self.classes = Some(vec![buffer]);
                    buffer = String::new();
                } else if let Some(ref mut classes) = self.classes {
                    // classes exists; borrow as mut ref and push buffer
                    classes.push(buffer);
                    buffer = String::new();
                }
            }
            ParserMode::Id => {
                if self.id.is_none() {
                    // id is none; set to buffer
                    self.id = Some(buffer);
                    buffer = String::new();
                }
            }
            ParserMode::Attribute => {
                if self.attributes.is_none() {
                    // attributes is none; init attributes with Some(vec![buffer])
                    self.attributes = Some(vec![buffer]);
                    buffer = String::new();
                } else if let Some(ref mut attributes) = self.attributes {
                    // attributes exists; borrow as mut ref and push buffer
                    attributes.push(buffer);
                    buffer = String::new();
                }
            }
        }

        buffer
    }

    /// Render state to HTML.
    pub fn render(self) -> String {
        let mut class_string = String::new();
        let mut id_string = String::new();
        let mut attributes_string = String::new();

        if let Some(classes) = self.classes {
            class_string = "class=\"".to_string();

            for class in classes {
                class_string.push_str(&(class + " "));
            }

            class_string += "\"";
        }

        if let Some(id) = self.id {
            id_string = format!("id=\"{id}\"");
        }

        if let Some(attributes) = self.attributes {
            for attribute in attributes {
                attributes_string.push_str(&attribute);
            }
        }

        format!(
            "<{} {class_string} {id_string} {attributes_string}>",
            self.tag
        )
    }
}

/// The mode of the [`Selector`] parser.
#[derive(PartialEq, Eq)]
pub enum ParserMode {
    None,
    Class,
    Id,
    Attribute,
}

/// A simple parser for CSS selectors
pub struct Selector(String);

impl Selector {
    /// Create a new [`Selector`].
    pub fn new(input: String) -> Self {
        Self(input)
    }

    /// Begin parsing the selector.
    pub fn parse(self) -> SelectorState {
        let mut state = SelectorState {
            tag: String::new(),
            classes: None,
            id: None,
            attributes: None,
        };

        // parse
        let mut chars = self.0.chars();
        let mut mode: ParserMode = ParserMode::None;
        let mut buffer: String = String::new();

        while let Some(char) = chars.next() {
            match char {
                '.' => {
                    buffer = state.try_save(mode, buffer.clone());
                    mode = ParserMode::Class
                }
                '#' => {
                    buffer = state.try_save(mode, buffer.clone());
                    mode = ParserMode::Id
                }
                '[' => {
                    buffer = state.try_save(mode, buffer.clone());
                    mode = ParserMode::Attribute
                }
                ']' => {
                    buffer = state.try_save(mode, buffer.clone());
                    mode = ParserMode::None
                }
                _ => buffer.push(char),
            }
        }

        // return
        state.try_save(mode, buffer.clone()); // one last save to catch the ending stuff
        state
    }
}