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.clone()));
buffer.clear();
}
in_expr = true;
}
('}', true) => {
nodes.push(RenderNode::Expr(buffer.clone()));
buffer.clear();
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();
if tag_name == "text-group" {
return tag.children()
.all(parser)
.iter()
.filter_map(|child| build_ast(child, parser))
.next(); }
let attrs = tag
.attributes()
.iter()
.filter_map(|(k, v)| v.map(|v| (k.as_ref().to_string(), v.as_ref().to_string())))
.collect();
let mut children = Vec::new();
let mut text_buffer = String::new();
for child in tag.children().all(parser) {
match build_ast(child, parser) {
Some(RenderNode::Text(text)) => {
text_buffer.push_str(&text);
}
Some(node) => {
if !text_buffer.is_empty() {
children.push(RenderNode::Text(std::mem::take(&mut text_buffer)));
}
children.push(node);
}
None => {}
}
}
if !text_buffer.is_empty() {
children.push(RenderNode::Text(text_buffer));
}
Some(RenderNode::Element {
tag_name,
attrs,
children,
})
}
Node::Raw(bytes) => {
let text = String::from_utf8_lossy(bytes.as_bytes()).trim().to_string();
if text.is_empty() {
None
} else {
let parts = parse_text_expr(&text);
match parts.len() {
0 => None,
1 => Some(parts.into_iter().next().unwrap()),
_ => {
let mut children = Vec::new();
let mut current_text = String::new();
for part in parts {
match part {
RenderNode::Text(text) => current_text.push_str(&text),
RenderNode::Expr(expr) => {
if !current_text.is_empty() {
children.push(RenderNode::Text(std::mem::take(&mut current_text)));
}
children.push(RenderNode::Expr(expr));
}
_ => {}
}
}
if !current_text.is_empty() {
children.push(RenderNode::Text(current_text));
}
match children.len() {
0 => None,
1 => Some(children.into_iter().next().unwrap()),
_ => Some(RenderNode::Element {
tag_name: "fragment".to_string(),
attrs: Vec::new(),
children,
}),
}
}
}
}
}
Node::Comment(_) => None,
}
}
pub fn parse_render_block(html: &str) -> Result<Vec<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(children)
}