Skip to main content

designtime_jsx/
parser.rs

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            // Skip artificial text-group elements
54            if tag_name == "text-group" {
55                return tag.children()
56                    .all(parser)
57                    .iter()
58                    .filter_map(|child| build_ast(child, parser))
59                    .next(); // Just take the first child
60            }
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}