1use tl::{Node, Parser, ParserOptions};
2
3#[derive(Debug)]
4pub enum RenderNode {
5 Element {
6 tag_name: String,
7 attrs: Vec<(String, String)>,
8 children: Vec<RenderNode>,
9 },
10 Text(String),
11 Expr(String),
12}
13
14fn parse_text_expr(text: &str) -> Vec<RenderNode> {
15 let mut nodes = Vec::new();
16 let mut buffer = String::new();
17 let mut in_expr = false;
18
19 for c in text.chars() {
20 match (c, in_expr) {
21 ('{', false) => {
22 if !buffer.is_empty() {
23 nodes.push(RenderNode::Text(buffer.clone()));
24 buffer.clear();
25 }
26 in_expr = true;
27 }
28 ('}', true) => {
29 nodes.push(RenderNode::Expr(buffer.clone()));
30 buffer.clear();
31 in_expr = false;
32 }
33 _ => buffer.push(c),
34 }
35 }
36
37 if !buffer.is_empty() {
38 nodes.push(if in_expr {
39 RenderNode::Expr(buffer)
40 } else {
41 RenderNode::Text(buffer)
42 });
43 }
44
45 nodes
46}
47
48fn build_ast(node: &Node, parser: &Parser) -> Option<RenderNode> {
49 match node {
50 Node::Tag(tag) => {
51 let tag_name = tag.name().as_utf8_str().to_string();
52
53 if tag_name == "text-group" {
55 return tag.children()
56 .all(parser)
57 .iter()
58 .filter_map(|child| build_ast(child, parser))
59 .next(); }
61
62 let attrs = tag
63 .attributes()
64 .iter()
65 .filter_map(|(k, v)| v.map(|v| (k.as_ref().to_string(), v.as_ref().to_string())))
66 .collect();
67
68 let mut children = Vec::new();
69 let mut text_buffer = String::new();
70
71 for child in tag.children().all(parser) {
72 match build_ast(child, parser) {
73 Some(RenderNode::Text(text)) => {
74 text_buffer.push_str(&text);
75 }
76 Some(node) => {
77 if !text_buffer.is_empty() {
78 children.push(RenderNode::Text(std::mem::take(&mut text_buffer)));
79 }
80 children.push(node);
81 }
82 None => {}
83 }
84 }
85
86 if !text_buffer.is_empty() {
87 children.push(RenderNode::Text(text_buffer));
88 }
89
90 Some(RenderNode::Element {
91 tag_name,
92 attrs,
93 children,
94 })
95 }
96 Node::Raw(bytes) => {
97 let text = String::from_utf8_lossy(bytes.as_bytes()).trim().to_string();
98 if text.is_empty() {
99 None
100 } else {
101 let parts = parse_text_expr(&text);
102 match parts.len() {
103 0 => None,
104 1 => Some(parts.into_iter().next().unwrap()),
105 _ => {
106 let mut children = Vec::new();
107 let mut current_text = String::new();
108
109 for part in parts {
110 match part {
111 RenderNode::Text(text) => current_text.push_str(&text),
112 RenderNode::Expr(expr) => {
113 if !current_text.is_empty() {
114 children.push(RenderNode::Text(std::mem::take(&mut current_text)));
115 }
116 children.push(RenderNode::Expr(expr));
117 }
118 _ => {}
119 }
120 }
121
122 if !current_text.is_empty() {
123 children.push(RenderNode::Text(current_text));
124 }
125
126 match children.len() {
127 0 => None,
128 1 => Some(children.into_iter().next().unwrap()),
129 _ => Some(RenderNode::Element {
130 tag_name: "fragment".to_string(),
131 attrs: Vec::new(),
132 children,
133 }),
134 }
135 }
136 }
137 }
138 }
139 Node::Comment(_) => None,
140 }
141}
142
143pub fn parse_render_block(html: &str) -> Result<Vec<RenderNode>, Box<dyn std::error::Error>> {
144 let dom = tl::parse(html, ParserOptions::default())?;
145 let parser = dom.parser();
146
147 let children = dom
148 .nodes()
149 .iter()
150 .filter_map(|node| build_ast(node, parser))
151 .collect();
152
153 Ok(children)
154}