rocal_ui/html/
to_tokens.rs

1use 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("&amp;"),
27                            '"' => output.push_str("&quot;"),
28                            '\'' => output.push_str("&#39;"),
29                            '>' => output.push_str("&gt;"),
30                            '<' => output.push_str("&lt;"),
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}