rust_fel/
rsx.rs

1use crate::element::Element;
2use crate::props::Props;
3use std::fmt;
4
5#[doc(hidden)]
6#[derive(Debug, Default, Clone)]
7struct StackElement {
8    val: String,
9    arena_position: usize,
10}
11
12/// Takes a string which is formatted in an HTML-like structure.
13/// Parses the string contents and builds a [rust_fel::ArenaTree](../rsx/struct.ArenaTree.html)
14/// # Arguments
15///
16/// * `html_string` - Must have a parent wrapping html element. All text must have a wrapping element. Text and non text elements cannot be siblings.
17///
18/// # Examples
19///```ignore
20///   // <div></div> <div></div> will not work.
21///   // <div><div></div></div> will work.
22///   // <div> Hi <span>Hello</span></div> will not work.
23///   // <div> </span>Hi</span><span>Hello</span></div> will work.
24///
25///   let arena_tree =
26///       parse_html_to_arena_tree("<div |class=classname|><div>here is some text</div></div>".to_owned());
27///       assert_eq!(arena_tree.arena[2].parent, 1);
28///   let arena_tree =
29///      parse_html_to_arena_tree("<div><div><span>here is some text</span></div></div>".to_owned());
30///      assert_eq!(arena_tree.arena[3].parent, 2);
31///```
32#[doc(hidden)]
33pub fn parse_html_to_arena_tree(html_string: String) -> ArenaTree {
34    let mut tokens = html_string.chars().peekable();
35    let mut element_type: String = String::new();
36    let mut is_open_tag: bool = false;
37    let mut has_text: bool = false;
38    let mut text: String = String::new();
39    let mut stack: Vec<StackElement> = vec![];
40    let mut arena_tree: ArenaTree = ArenaTree::default();
41    let mut has_attributes: bool = false;
42    let mut attributes: String = String::new();
43
44    while let Some(character) = tokens.next() {
45        let string_character = character.to_string();
46
47        if string_character == "<" {
48            if has_text {
49                // This pattern may need to be function-ized since we may need to repeat.
50                // If the child is a text elment we insert
51                // We need to let arena tree know the positions of the parent
52                // In arenatree.insert we set the parent's and the children of the element being inserted
53                if !stack.is_empty() {
54                    arena_tree.set_current_parent_idx(stack.last().unwrap().arena_position);
55                } else {
56                    arena_tree.set_current_parent_idx(0);
57                }
58                arena_tree.insert(Node {
59                    element_type: "TEXT_ELEMENT".to_owned(),
60                    text: Some(text.clone()),
61                    ..Default::default()
62                });
63            }
64            if tokens.peek().unwrap().to_string() != "/" {
65                is_open_tag = true;
66            }
67            if tokens.peek().unwrap().to_string() == "/" {
68                is_open_tag = false;
69                stack.pop();
70            }
71
72            has_text = false;
73            text = "".to_owned();
74            continue;
75        }
76
77        // Either last element of string or there will be a text child or another element
78        if string_character == ">" {
79            if element_type != "" {
80                let next_token = tokens.peek().unwrap().to_string();
81
82                // Here we must have text if it's not another element
83                if next_token != "<" {
84                    has_text = true;
85                };
86
87                // Here we insert our element type and all attributes collected
88                let el = element_type.clone();
89                if !stack.is_empty() {
90                    arena_tree.set_current_parent_idx(stack.last().unwrap().arena_position);
91                } else {
92                    arena_tree.set_current_parent_idx(0);
93                }
94
95                let mut class_name = None;
96                let mut href = None;
97                let mut id = None;
98                let mut src = None;
99                let mut role = None;
100                let mut type_attr = None;
101                let mut data_cy = None;
102                if !attributes.is_empty() {
103                    let attributes_split = attributes.split(' ').filter(|s| !s.is_empty());
104                    for attribute in attributes_split {
105                        let attr = attribute.split('=').collect::<Vec<&str>>();
106                        match attr[0] {
107                            "class" => class_name = Some(attr[1].to_owned()),
108                            "href" => href = Some(attr[1].to_owned()),
109                            "src" => src = Some(attr[1].to_owned()),
110                            "role" => role = Some(attr[1].to_owned()),
111                            "type" => type_attr = Some(attr[1].to_owned()),
112                            "id" => id = Some(attr[1].to_owned()),
113                            "data-cy" => data_cy = Some(attr[1].to_owned()),
114                            // Try Rc<RefCell>attribute handlers at the top of this function.
115                            // match attribute handlers borrow_mut()
116                            // "on_click" => {
117                            //   match attribute_handlers {
118                            //     Some(the_vec) => {
119                            //       let idx = attr[1].to_owned().parse::<usize>().unwrap();
120                            //       let handler = the_vec.into_iter().nth(idx).unwrap();
121                            //       on_click = Some(handler);
122                            //     }
123                            //     None => (),
124                            //   };
125                            // }
126                            _ => (),
127                        };
128                    }
129                }
130                arena_tree.insert(Node {
131                    element_type: element_type.clone(),
132                    class_name,
133                    href,
134                    data_cy,
135                    id,
136                    src,
137                    role,
138                    type_attr,
139                    ..Default::default()
140                });
141                stack.push(StackElement {
142                    val: el,
143                    arena_position: arena_tree.arena.len() - 1,
144                });
145                // Reset everything so we have no data and can reade child element
146                element_type = String::new();
147                attributes = String::new();
148                has_attributes = false;
149            }
150            continue;
151        }
152
153        if string_character == "|" && !has_attributes {
154            has_attributes = true;
155            continue;
156        }
157        // Down here we decide what types of variables to push to
158        if is_open_tag && !has_text {
159            if string_character != " " && !has_attributes {
160                element_type.push_str(&string_character);
161                continue;
162            }
163
164            if has_attributes && string_character != "|" {
165                attributes.push_str(&string_character);
166                continue;
167            }
168        }
169
170        if has_text {
171            text.push_str(&string_character);
172            continue;
173        }
174    }
175
176    if !stack.is_empty() {
177        panic!("Your HTML is not formed correctly");
178    }
179
180    arena_tree
181}
182
183#[cfg(test)]
184#[test]
185pub fn is_parent_correct() {
186    let arena_tree = parse_html_to_arena_tree(
187        "<div |class=classname|><div>here is some text</div></div>".to_owned(),
188    );
189    assert_eq!(arena_tree.arena[2].parent, 1);
190    let arena_tree =
191        parse_html_to_arena_tree("<div><div>here is some text</div><span></span></div>".to_owned());
192    assert_eq!(arena_tree.arena[2].parent, 1);
193    let arena_tree =
194        parse_html_to_arena_tree("<div><div><span>here is some text</span></div></div>".to_owned());
195    assert_eq!(arena_tree.arena[3].parent, 2);
196    let arena_tree = parse_html_to_arena_tree("<div>Hi there</div>".to_owned());
197    assert_eq!(arena_tree.arena[1].parent, 0);
198}
199
200#[cfg(test)]
201#[test]
202#[should_panic(expected = "Your HTML is not formed correctly")]
203pub fn is_correct_html() {
204    parse_html_to_arena_tree("<div |class=classname|><div>here is some text</div>".to_owned());
205    parse_html_to_arena_tree("<div><div>here is some text<div></div>".to_owned());
206}
207
208#[cfg(test)]
209#[test]
210pub fn is_correct_attributes() {
211    let arena_tree = parse_html_to_arena_tree(
212        "<div |class=classname href=https://www.google.com |><div |class=hi href=https://www.googles.com |>here is some text</div></div>"
213            .to_owned(),
214    );
215    assert_eq!(
216        arena_tree.arena[0].class_name.as_ref().unwrap(),
217        &"classname".to_owned()
218    );
219    assert_eq!(
220        arena_tree.arena[0].href.as_ref().unwrap(),
221        &"https://www.google.com".to_owned()
222    );
223    assert_eq!(
224        arena_tree.arena[1].class_name.as_ref().unwrap(),
225        &"hi".to_owned()
226    );
227    assert_eq!(
228        arena_tree.arena[1].href.as_ref().unwrap(),
229        &"https://www.googles.com".to_owned()
230    );
231    assert_ne!(
232        arena_tree.arena[1].href.as_ref().unwrap(),
233        &"https://www.google.com".to_owned()
234    );
235    let arena_tree =
236        parse_html_to_arena_tree("<script |src=https://www.google.com |></script>".to_owned());
237    assert_eq!(
238        arena_tree.arena[0].src.as_ref().unwrap(),
239        &"https://www.google.com".to_owned()
240    );
241
242    let arena_tree = parse_html_to_arena_tree("<button | type=button role=button |><button | type=button role=button |></button></button>".to_owned());
243    assert_eq!(
244        arena_tree.arena[0].type_attr.as_ref().unwrap(),
245        &"button".to_owned()
246    );
247    assert_eq!(
248        arena_tree.arena[0].role.as_ref().unwrap(),
249        &"button".to_owned()
250    );
251    assert_eq!(
252        arena_tree.arena[1].type_attr.as_ref().unwrap(),
253        &"button".to_owned()
254    );
255    assert_eq!(
256        arena_tree.arena[1].role.as_ref().unwrap(),
257        &"button".to_owned()
258    );
259    let arena_tree = parse_html_to_arena_tree("<button | data-cy=cypress role=button |><button | data-cy=cypress type=button role=button |></button></button>".to_owned());
260    assert_eq!(
261        arena_tree.arena[0].data_cy.as_ref().unwrap(),
262        &"cypress".to_owned()
263    );
264    assert_eq!(
265        arena_tree.arena[1].data_cy.as_ref().unwrap(),
266        &"cypress".to_owned()
267    );
268}
269
270/// Create a Virtual Dom of [rust_fel::Element](../rsx/struct.Element.html) from a string of [HTML](https://developer.mozilla.org/en-US/docs/Web/HTML]).
271/// # Arguments
272///
273/// * `html_string` - Must have a parent wrapping [HTML Element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element). All text must have a wrapping [HTML Element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element). Text and non text [HTML Elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element) cannot be siblings.
274/// # Examples
275/// ```
276/// use rust_fel::html;
277/// let html = html(
278///      "<div |class=classname|><span |role=button|>Span Text</span><p>Paragraph Text</p></div>"
279///          .to_owned(),
280///  );
281///  assert_eq!(html.html_type, "div".to_owned());
282///  assert_eq!(html.props.class_name.unwrap(), "classname".to_owned());
283///
284///  let children = html.props.children.unwrap();
285///  let first_child = children.iter().nth(0);
286///  let second_child = children.iter().nth(1);
287///  assert_eq!(first_child.unwrap().html_type, "span");
288///  assert_eq!(first_child.unwrap().props.role.as_ref().unwrap(), "button");
289///  assert_eq!(second_child.unwrap().html_type, "p");
290///
291///  let second_childs_child = &second_child
292///      .unwrap()
293///      .props
294///      .children
295///      .iter()
296///      .nth(0)
297///      .unwrap()
298///      .iter()
299///      .nth(0)
300///      .unwrap();
301///  assert_eq!(second_childs_child.html_type, "TEXT_ELEMENT");
302/// ```
303
304pub fn html(html_string: String) -> Element {
305    let arena_tree = parse_html_to_arena_tree(html_string);
306    arena_tree.create_element_from_tree()
307}
308
309/// A structure which builds an ```arena``` ([std::vec::Vec](https://doc.rust-lang.org/std/vec/struct.Vec.html)) of [rust_fel::Node](../rsx/struct.Node.html)'s that represent a tree structure.
310/// # Examples
311/// ```ignore
312///   arena_tree.insert(Node {
313///     element_type: element_type.clone(),
314///     class_name,
315///     href,
316///     data_cy,
317///     id,
318///     src,
319///     role,
320///     type_attr,
321///     ..Default::default()
322///     });
323/// ```
324#[doc(hidden)]
325#[derive(Debug, Default)]
326pub struct ArenaTree {
327    current_parent_idx: usize,
328    arena: Vec<Node>,
329}
330
331impl ArenaTree {
332    fn set_current_parent_idx(&mut self, idx: usize) {
333        self.current_parent_idx = idx;
334    }
335
336    fn insert(&mut self, mut node: Node) {
337        node.parent = self.current_parent_idx;
338        node.idx = self.arena.len();
339        self.arena.push(node);
340        let child_index = self.arena.len() - 1;
341        let parent_node = &mut self.arena[self.current_parent_idx];
342        if child_index > 0 {
343            parent_node.add_child(child_index);
344        }
345    }
346
347    fn create_element_from_tree(&self) -> Element {
348        let arena = &self.arena;
349
350        fn children(node: &Node, arena: &[Node]) -> Option<Vec<Element>> {
351            Some(
352                node.children
353                    .iter()
354                    .map(|child| create(&arena[child.to_owned()], &arena))
355                    .collect::<Vec<Element>>(),
356            )
357        };
358
359        fn create(node: &Node, arena: &[Node]) -> Element {
360            let text = match &node.text {
361                Some(x) => Some(x.to_owned()),
362                None => None,
363            };
364
365            let class_name = match &node.class_name {
366                Some(x) => Some(x.to_owned()),
367                None => None,
368            };
369
370            let href = match &node.href {
371                Some(x) => Some(x.to_owned()),
372                None => None,
373            };
374
375            let data_cy = match &node.data_cy {
376                Some(x) => Some(x.to_owned()),
377                None => None,
378            };
379
380            let id = match &node.id {
381                Some(x) => Some(x.to_owned()),
382                None => None,
383            };
384
385            let src = match &node.src {
386                Some(x) => Some(x.to_owned()),
387                None => None,
388            };
389
390            let type_attr = match &node.type_attr {
391                Some(x) => Some(x.to_owned()),
392                None => None,
393            };
394
395            let role = match &node.role {
396                Some(x) => Some(x.to_owned()),
397                None => None,
398            };
399
400            Element {
401                html_type: node.element_type.clone(),
402                props: Props {
403                    children: children(node, arena),
404                    text,
405                    class_name,
406                    href,
407                    data_cy,
408                    id,
409                    src,
410                    type_attr,
411                    role,
412                    ..Default::default()
413                },
414            }
415        }
416        let node = &arena[0];
417        create(node, arena)
418    }
419}
420/// A ```Node``` is an intermediary representation of an HTML element.
421/// A ```Node``` is constructed as a result of a html string being parsed. It will be inserted into a arena tree after initialization.
422/// # Examples
423/// ```ignore
424///   arena_tree.insert(Node {
425///     element_type: element_type.clone(),
426///     class_name,
427///     href,
428///     data_cy,
429///     id,
430///     src,
431///     role,
432///     type_attr,
433///     ..Default::default()
434///     });
435/// ```
436
437#[doc(hidden)]
438#[derive(Default)]
439pub struct Node {
440    idx: usize,
441    element_type: String,
442    parent: usize,
443    children: Vec<usize>,
444    text: Option<String>,
445    id: Option<String>,
446    class_name: Option<String>,
447    href: Option<String>,
448    src: Option<String>,
449    type_attr: Option<String>,
450    role: Option<String>,
451    data_cy: Option<String>,
452    // on_click: Option<ClosureProp>,
453}
454
455impl fmt::Debug for Node {
456    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
457        write!(
458            f,
459            "{:#?}, {:#?} this is a node",
460            self.element_type, self.class_name
461        )
462    }
463}
464
465impl Node {
466    fn add_child(&mut self, child_idx: usize) {
467        self.children.push(child_idx);
468    }
469}