elvis_shared/tree/
web.rs

1//! parser in #[cfg(feature = "web")]
2use crate::err::Error;
3use crate::{Node, Serde};
4use std::cell::RefCell;
5use std::collections::HashMap;
6use std::rc::{Rc, Weak};
7
8/// Extra html stream
9#[derive(Debug)]
10struct Extra<'e> {
11    pub end: bool,
12    pub pos: usize,
13    pub tag: &'e str,
14}
15
16/// process of parsing children
17#[derive(Eq, PartialEq)]
18enum ChildrenProcess {
19    BeginTag,
20    CloseTag,
21    None,
22    Plain,
23}
24
25/// process of parsing tag
26enum TagProcess {
27    Attrs,
28    Quote,
29    None,
30    Tag,
31}
32
33/// Deserialize Node from html string
34///
35/// `attrs` field follows MDN doc [HTML attribute refference][1],
36/// all values are `String` in "".
37///
38/// [1]: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes#Boolean_Attributes
39fn rde<'t>(
40    h: &'t str,
41    pre: Option<Weak<RefCell<Node>>>,
42) -> Result<(Rc<RefCell<Node>>, Option<Extra<'t>>), Error> {
43    let mut pos = 0_usize;
44    if h.is_empty() {
45        return Ok((Rc::new(RefCell::new(Node::default())), None));
46    } else if h.find("</").is_none() {
47        return Ok((Rc::new(RefCell::new(self::plain(h, pre))), None));
48    }
49
50    // the return-will tree
51    let tree = Rc::new(RefCell::new(Node::default()));
52    let tw = Rc::downgrade(&tree);
53    let (tag, attrs) = self::tag(&h[pos..], &mut pos)?;
54
55    // parse f*cking children
56    let mut children: Vec<Rc<RefCell<Node>>> = vec![];
57    let mut cext = self::ch(&h[pos..], Some(tw.clone()), tag, &mut children)?;
58
59    // parse parallel children
60    pos += cext.pos;
61    while !cext.end {
62        cext = self::ch(&h[pos..], Some(tw.clone()), tag, &mut children)?;
63        pos += cext.pos;
64    }
65
66    // communite with child parser
67    let ext = if (pos + 1) != h.len() {
68        Some(Extra {
69            end: false,
70            pos,
71            tag: cext.tag,
72        })
73    } else {
74        None
75    };
76
77    let mut bt = tree.borrow_mut();
78    bt.pre = pre;
79    bt.tag = tag.to_string();
80    bt.attrs = attrs;
81    bt.children = children;
82    drop(bt);
83
84    Ok((tree, ext))
85}
86
87/// push child from html stream
88fn ch<'t>(
89    cht: &'t str,
90    pre: Option<Weak<RefCell<Node>>>,
91    tag: &'t str,
92    children: &mut Vec<Rc<RefCell<Node>>>,
93) -> Result<Extra<'t>, Error> {
94    let mut itag = tag;
95    let mut process = ChildrenProcess::None;
96    let (mut t, mut c) = ((0, 0), (0, 0));
97    for (p, q) in cht.chars().enumerate() {
98        match q {
99            '<' => {
100                if process == ChildrenProcess::Plain {
101                    c.1 = p;
102                }
103
104                process = ChildrenProcess::BeginTag;
105                t.0 = p;
106                t.1 = p;
107            }
108            '/' => {
109                if &cht[t.0..(t.0 + 1)] == "<" {
110                    process = ChildrenProcess::CloseTag;
111                } else if process != ChildrenProcess::Plain {
112                    return Err(Error::DeserializeHtmlError(format!(
113                        "children parse failed {}, cht: {}, process: {}",
114                        &tag,
115                        &cht,
116                        &cht[t.0..(t.0 + 1)],
117                    )));
118                }
119            }
120            '>' => {
121                t.1 = p;
122                match process {
123                    ChildrenProcess::BeginTag => {
124                        let (tree, ext) = self::rde(&cht[t.0..], pre.clone())?;
125                        children.push(tree);
126
127                        // communite with father node
128                        if let Some(cext) = ext {
129                            return Ok(Extra {
130                                end: false,
131                                tag: cext.tag,
132                                pos: cext.pos + t.0,
133                            });
134                        }
135                    }
136                    ChildrenProcess::CloseTag => {
137                        // verify tag, trim:  "/ tag"
138                        itag = &cht[(t.0 + 1)..t.1].trim()[1..].trim()[..];
139                        if itag != tag {
140                            return Err(Error::DeserializeHtmlError(format!(
141                                "children parse failed {}, cht: {}, close_tag: {}",
142                                &tag, &cht, &itag
143                            )));
144                        } else if !cht[c.0..c.1].is_empty() {
145                            children.push(Rc::new(RefCell::new(self::plain(&cht[c.0..c.1], pre))));
146                        }
147
148                        return Ok(Extra {
149                            end: true,
150                            pos: p,
151                            tag: itag,
152                        });
153                    }
154                    _ => {
155                        // None and Plain
156                    }
157                }
158            }
159            x if !x.is_whitespace() => {
160                match process {
161                    ChildrenProcess::None => {
162                        process = ChildrenProcess::Plain;
163                        c.0 = p;
164                        c.1 = p;
165                    }
166                    ChildrenProcess::Plain => {
167                        c.1 = p;
168                    }
169                    _ => {
170                        // tag conditions
171                    }
172                }
173            }
174            _ => {
175                // invalid chars
176            }
177        }
178    }
179    Ok(Extra {
180        end: true,
181        pos: cht.len(),
182        tag: itag,
183    })
184}
185
186/// generate palin text
187fn plain<'t>(h: &'t str, pre: Option<Weak<RefCell<Node>>>) -> Node {
188    let mut attrs = HashMap::<String, String>::new();
189    attrs.insert("text".into(), h.into());
190
191    Node {
192        pre,
193        tag: "plain".into(),
194        attrs,
195        children: vec![],
196    }
197}
198
199/// parse html tag
200fn tag<'t>(h: &'t str, pos: &mut usize) -> Result<(&'t str, HashMap<String, String>), Error> {
201    let (mut tag, mut key, mut value) = ((0, 0), (0, 0), (0, 0));
202    let mut attrs = HashMap::<String, String>::new();
203    let mut process = TagProcess::None;
204    for (p, q) in h.chars().enumerate() {
205        match q {
206            '<' => {
207                process = TagProcess::Tag;
208                tag.0 = p + 1;
209                tag.1 = p + 1;
210            }
211            '>' => {
212                match process {
213                    TagProcess::Tag => tag.1 = p,
214                    TagProcess::Attrs => {
215                        if !&h[key.0..key.1].trim().is_empty() {
216                            attrs.insert(
217                                h[key.0..key.1].trim().to_string(),
218                                h[value.0..value.1].trim().into(),
219                            );
220                        }
221                    }
222                    _ => {}
223                }
224
225                *pos = *pos + p + 1;
226                return Ok((&h[tag.0..tag.1].trim(), attrs));
227            }
228            '"' => match process {
229                TagProcess::Quote => {
230                    process = TagProcess::Attrs;
231                    value.1 = p;
232                }
233                _ => {
234                    value.0 = p + 1;
235                    value.1 = p + 1;
236                    process = TagProcess::Quote;
237                }
238            },
239            '=' => match process {
240                TagProcess::Attrs => key.1 = p,
241                _ => {
242                    return Err(Error::DeserializeHtmlError(format!(
243                        "html tag parse failed: {}, html: {}",
244                        &h[tag.0..tag.1],
245                        &h
246                    )))
247                }
248            },
249            x if x.is_whitespace() => match process {
250                TagProcess::Tag => {
251                    if h[tag.0..tag.1].trim().is_empty() {
252                        tag.1 = p;
253                    } else {
254                        process = TagProcess::Attrs;
255                        key.0 = p + 1;
256                        key.1 = p + 1;
257                    }
258                }
259                TagProcess::Quote => {
260                    value.1 = p;
261                }
262                TagProcess::Attrs => {
263                    if (key.1 - key.0 != 0) && (value.1 - value.0 != 0) {
264                        attrs.insert(
265                            h[key.0..key.1].trim().to_string(),
266                            h[value.0..value.1].trim().into(),
267                        );
268                        key.0 = p;
269                        key.1 = p;
270                    }
271                }
272                _ => {}
273            },
274            x if !x.is_whitespace() => match process {
275                TagProcess::Tag => {
276                    tag.1 = p + 1;
277                }
278                TagProcess::Quote => {
279                    value.1 = p;
280                }
281                TagProcess::Attrs => {
282                    if value.0 == 0 {
283                        key.1 = p;
284                    } else {
285                        value.1 = p;
286                    }
287                }
288                _ => {}
289            },
290            _ => {
291                return Err(Error::DeserializeHtmlError(format!(
292                    "html tag parse failed: {}, html: {}, char: {}",
293                    &h[tag.0..tag.1],
294                    &h,
295                    &q
296                )))
297            }
298        }
299    }
300
301    Err(Error::DeserializeHtmlError(format!(
302        "html tag parse failed: {}, html: {}",
303        &h[tag.0..tag.1],
304        &h
305    )))
306}
307
308impl<'t> Serde<Node, String, Error> for Node {
309    fn de(h: String) -> Result<Node, Error> {
310        Ok(self::rde(Box::leak(Box::new(h)), None)?
311            .0
312            .borrow()
313            .to_owned())
314    }
315
316    fn ser(&self) -> String {
317        let mut html = "".to_string();
318        let mut attrs = " ".to_string();
319        let mut children = "".to_string();
320
321        // plain text
322        if self.tag == "plain" {
323            html.push_str(&self.attrs.get("text").unwrap_or(&"".into()));
324        } else {
325            for (k, v) in self.attrs.iter() {
326                attrs.push_str(&format!("{}=\"{}\" ", k, v));
327            }
328
329            for i in &self.children {
330                children.push_str(&i.borrow().to_owned().ser());
331            }
332
333            if attrs.trim().is_empty() {
334                attrs.drain(..);
335            }
336
337            html.push_str(&format!(
338                "<{}{}>{}</{}>",
339                &self.tag,
340                attrs.trim_end(),
341                children,
342                &self.tag,
343            ));
344        }
345
346        html
347    }
348}