use tl::{Node, Parser, ParserOptions};
#[derive(Debug)]
pub enum RenderNode {
Element {
tag_name: String,
attrs: Vec<(String, String)>,
children: Vec<RenderNode>,
},
Text(String),
Expr(String),
}
fn parse_text_expr(text: &str) -> Vec<RenderNode> {
let mut nodes = Vec::new();
let mut buffer = String::new();
let mut in_expr = false;
for c in text.chars() {
match (c, in_expr) {
('{', false) => {
if !buffer.is_empty() {
nodes.push(RenderNode::Text(buffer));
buffer = String::new();
}
in_expr = true;
}
('}', true) => {
nodes.push(RenderNode::Expr(buffer));
buffer = String::new();
in_expr = false;
}
_ => buffer.push(c),
}
}
if !buffer.is_empty() {
nodes.push(if in_expr {
RenderNode::Expr(buffer)
} else {
RenderNode::Text(buffer)
});
}
nodes
}
fn build_ast(node: &Node, parser: &Parser) -> Option<RenderNode> {
match node {
Node::Tag(tag) => {
let tag_name = tag.name().as_utf8_str().to_string();
let attrs = tag
.attributes()
.iter()
.filter_map(|(k, v)| v.map(|v| (k.as_ref().to_string(), v.as_ref().to_string())))
.collect();
let children = tag
.children()
.all(parser)
.iter()
.filter_map(|node| build_ast(node, parser))
.collect();
Some(RenderNode::Element {
tag_name,
attrs,
children,
})
}
Node::Raw(bytes) => {
let text = String::from_utf8_lossy(bytes.as_bytes());
let trimmed = text.trim();
if trimmed.is_empty() {
None
} else {
let nodes = parse_text_expr(trimmed);
if nodes.is_empty() {
None
} else if nodes.len() == 1 {
nodes.into_iter().next()
} else {
Some(RenderNode::Element {
tag_name: "text-group".to_string(),
attrs: Vec::new(),
children: nodes,
})
}
}
}
Node::Comment(_) => None,
}
}
pub fn parse_render_block(html: &str) -> Result<RenderNode, Box<dyn std::error::Error>> {
let dom = tl::parse(html, ParserOptions::default())?;
let parser = dom.parser();
let children = dom
.nodes()
.iter()
.filter_map(|node| build_ast(node, parser))
.collect();
Ok(RenderNode::Element {
tag_name: "root".to_string(),
attrs: Vec::new(),
children,
})
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let html = r#"
<design-box foo="bar">
Hello {user.first} {user.last}!
</design-box>
<design-button>Click me</design-button>
"#;
let ast = parse_render_block(html)?;
println!("{:#?}", ast);
Ok(())
}