rocal_ui/html/
to_tokens.rs1use crate::enums::html_element::HtmlElement;
2
3use super::{AttributeValue, Html, Lex};
4use proc_macro2::{Literal, Span, TokenStream};
5use quote::quote;
6use syn::{parse_str, Expr, Ident};
7
8pub trait ToTokens {
9 fn to_token_stream(&self) -> TokenStream;
10}
11
12impl ToTokens for Html {
13 fn to_token_stream(&self) -> TokenStream {
14 let mut stmts = Vec::<TokenStream>::new();
15 self.collect_stmts(&mut stmts);
16
17 quote! {
18 {
19 use std::fmt::Write;
20
21 let html_escape = |input: &str| -> String {
22 let mut output = String::with_capacity(input.len());
23
24 for c in input.chars() {
25 match c {
26 '&' => output.push_str("&"),
27 '"' => output.push_str("""),
28 '\'' => output.push_str("'"),
29 '>' => output.push_str(">"),
30 '<' => output.push_str("<"),
31 _ => output.push(c),
32 }
33 }
34
35 output
36 };
37
38 let mut html = String::new();
39 #(#stmts)*
40 html
41 }
42 }
43 }
44}
45
46impl Html {
47 fn collect_stmts(&self, out: &mut Vec<TokenStream>) {
48 let mut children = Vec::<TokenStream>::new();
49 for child in &self.children {
50 child.collect_stmts(&mut children);
51 }
52
53 match &self.value {
54 Lex::Tag {
55 element,
56 attributes,
57 } => {
58 if *element != HtmlElement::Fragment {
59 let element_literal = element.to_string();
60
61 out.push(quote! {
62 html.push_str("<");
63 html.push_str(#element_literal);
64 });
65 for attr in attributes {
66 let key = attr.key();
67
68 match attr.value() {
69 Some(AttributeValue::Text(text)) => {
70 let text = Literal::string(&text);
71 out.push(quote! {
72 write!(html, r#" {}="{}""#, #key, #text).unwrap();
73 });
74 }
75 Some(AttributeValue::Var(var)) => {
76 out.push(quote! {
77 write!(html, r#" {}="{}""#, #key, #var).unwrap();
78 });
79 }
80 None => out.push(quote! {
81 write!(html, " {}", #key).unwrap();
82 }),
83 };
84 }
85
86 if element.is_void() {
87 out.push(quote! {
88 html.push_str(" />");
89 });
90 } else {
91 out.push(quote! {
92 html.push_str(">");
93 });
94 }
95 }
96
97 if !element.is_void() {
98 for child in &self.children {
99 child.collect_stmts(out);
100 }
101
102 if *element != HtmlElement::Fragment {
103 let tag = format!("</{}>\n", &element);
104 out.push(quote! {
105 html.push_str(#tag);
106 });
107 }
108 }
109 }
110 Lex::SanitizedVar(var) => {
111 let var: Expr =
112 parse_str(var).expect(&format!("Cannot parse the variable: {}", &var));
113
114 out.push(quote! {
115 html.push_str(&html_escape(#var));
116 });
117 }
118 Lex::Var(var) => {
119 let var: Expr =
120 parse_str(var).expect(&format!("Cannot parse the variable: {}", &var));
121
122 out.push(quote! {
123 html.push_str(#var);
124 });
125 }
126 Lex::If(condition) => {
127 let condition: Expr = parse_str(&condition)
128 .expect(&format!("Cannot parse the condition: {}", &condition));
129
130 out.push(quote! {
131 if #condition {
132 #(#children)*
133 }
134 });
135 }
136 Lex::ElseIf(condition) => {
137 let condition: Expr = parse_str(&condition)
138 .expect(&format!("Cannot parse the condition: {}", &condition));
139
140 out.push(quote! {
141 else if #condition {
142 #(#children)*
143 }
144 });
145 }
146 Lex::Else => {
147 out.push(quote! {
148 else {
149 #(#children)*
150 }
151 });
152 }
153 Lex::For { var, iter } => {
154 let var = Ident::new(var, Span::call_site());
155 let iter: Expr =
156 parse_str(&iter).expect(&format!("Cannot parse the iter: {}", &iter));
157
158 out.push(quote! {
159 for #var in #iter {
160 #(#children)*
161 }
162 });
163 }
164 Lex::DocType => {
165 out.push(quote! {
166 html.push_str("<!DOCTYPE html>\n");
167 });
168 }
169 }
170 }
171}