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#[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 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 if string_character == ">" {
79 if element_type != "" {
80 let next_token = tokens.peek().unwrap().to_string();
81
82 if next_token != "<" {
84 has_text = true;
85 };
86
87 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 _ => (),
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 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 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
270pub 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#[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#[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 }
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}