html_to_string_macro/
lib.rs1use std::convert::TryFrom;
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::Expr;
6use syn_rsx::{parse, Node, NodeType};
7
8fn walk_nodes<'a>(nodes: &'a Vec<Node>, context: Option<NodeType>) -> (String, Vec<&'a Expr>) {
9 let mut out = String::new();
10 let mut values = vec![];
11
12 for node in nodes {
13 match node {
14 Node::Doctype(doctype) => {
15 let value = String::try_from(&doctype.value)
16 .expect("could not convert node value to string");
17 out.push_str(&format!("<!DOCTYPE {}>", value));
18 }
19 Node::Element(element) => {
20 let name = element.name.to_string();
21 out.push_str(&format!("<{}", name));
22
23 let (html_string, attribute_values) =
25 walk_nodes(&element.attributes, Some(NodeType::Attribute));
26 out.push_str(&html_string);
27 values.extend(attribute_values);
28 out.push('>');
29
30 match name.as_str() {
32 "area" | "base" | "br" | "col" | "embed" | "hr" | "img" | "input" | "link"
33 | "meta" | "param" | "source" | "track" | "wbr" => continue,
34 _ => (),
35 }
36
37 let (html_string, children_values) = walk_nodes(&element.children, None);
39 out.push_str(&html_string);
40 values.extend(children_values);
41
42 out.push_str(&format!("</{}>", name));
43 }
44 Node::Attribute(attribute) => {
45 out.push_str(&format!(" {}", attribute.key.to_string()));
46 if let Some(value) = &attribute.value {
47 out.push_str(r#"="{}""#);
48 values.push(value);
49 }
50 }
51 Node::Text(text) => {
52 out.push_str("{}");
53 values.push(&text.value);
54 }
55 Node::Fragment(fragment) => {
56 let (html_string, children_values) =
57 walk_nodes(&fragment.children, Some(NodeType::Fragment));
58 out.push_str(&html_string);
59 values.extend(children_values);
60 }
61 Node::Comment(comment) => {
62 out.push_str("<!-- {} -->");
63 values.push(&comment.value);
64 }
65 Node::Block(block) => {
66 if matches!(context, Some(NodeType::Attribute)) {
68 out.push(' ');
69 }
70
71 out.push_str("{}");
72 values.push(&block.value);
73 }
74 }
75 }
76
77 (out, values)
78}
79
80#[proc_macro]
96pub fn html(tokens: TokenStream) -> TokenStream {
97 match parse(tokens) {
98 Ok(nodes) => {
99 let (html_string, values) = walk_nodes(&nodes, None);
100 quote! { format!(#html_string, #(#values),*) }
101 }
102 Err(error) => error.to_compile_error(),
103 }
104 .into()
105}