Skip to main content

rbatis_codegen/codegen/
parser_html.rs

1use proc_macro2::TokenStream;
2use quote::{quote, ToTokens};
3use std::collections::BTreeMap;
4use syn::ItemFn;
5
6use crate::codegen::loader_html::{load_html, Element};
7use crate::codegen::proc_macro::TokenStream as MacroTokenStream;
8use crate::codegen::string_util::{concat_str, find_convert_string};
9use crate::codegen::syntax_tree_html::*;
10use crate::codegen::ParseArgs;
11use crate::error::Error;
12
13// Constants for common strings
14const SQL_TAG: &str = "sql";
15const INCLUDE_TAG: &str = "include";
16pub(crate) const MAPPER_TAG: &str = "mapper";
17const IF_TAG: &str = "if";
18const TRIM_TAG: &str = "trim";
19const BIND_TAG: &str = "bind";
20const WHERE_TAG: &str = "where";
21const CHOOSE_TAG: &str = "choose";
22const WHEN_TAG: &str = "when";
23const OTHERWISE_TAG: &str = "otherwise";
24const FOREACH_TAG: &str = "foreach";
25const SET_TAG: &str = "set";
26const CONTINUE_TAG: &str = "continue";
27const BREAK_TAG: &str = "break";
28const SELECT_TAG: &str = "select";
29const UPDATE_TAG: &str = "update";
30const INSERT_TAG: &str = "insert";
31const DELETE_TAG: &str = "delete";
32
33/// Loads HTML content into a map of elements keyed by their ID
34pub fn load_mapper_map(html: &str) -> Result<BTreeMap<String, Element>, Error> {
35    let elements = load_mapper_vec(html)?;
36    let mut sql_map = BTreeMap::new();
37    let processed_elements = include_replace(elements, &mut sql_map);
38
39    let mut m = BTreeMap::new();
40    for x in processed_elements {
41        if let Some(v) = x.attrs.get("id") {
42            m.insert(v.to_string(), x);
43        }
44    }
45    Ok(m)
46}
47
48/// Loads HTML content into a vector of elements
49pub fn load_mapper_vec(html: &str) -> Result<Vec<Element>, Error> {
50    let elements = load_html(html).map_err(|e| Error::from(e.to_string()))?;
51
52    let mut mappers = Vec::new();
53    for element in elements {
54        if element.tag == MAPPER_TAG {
55            mappers.extend(element.childs);
56        } else {
57            mappers.push(element);
58        }
59    }
60
61    Ok(mappers)
62}
63
64/// Handles include directives and replaces them with referenced content
65fn include_replace(
66    elements: Vec<Element>,
67    sql_map: &mut BTreeMap<String, Element>,
68) -> Vec<Element> {
69    elements
70        .into_iter()
71        .map(|mut element| {
72            match element.tag.as_str() {
73                SQL_TAG => {
74                    let id = element
75                        .attrs
76                        .get("id")
77                        .expect("[rbatis-codegen] <sql> element must have id!");
78                    sql_map.insert(id.clone(), element.clone());
79                }
80                INCLUDE_TAG => {
81                    element = IncludeTagNode::from_element(&element).process_include(sql_map);
82                }
83                _ => {
84                    if let Some(id) = element.attrs.get("id").filter(|id| !id.is_empty()) {
85                        sql_map.insert(id.clone(), element.clone());
86                    }
87                }
88            }
89
90            if !element.childs.is_empty() {
91                element.childs = include_replace(element.childs, sql_map);
92            }
93
94            element
95        })
96        .collect()
97}
98
99/// Parses HTML content into a function TokenStream
100pub fn parse_html(html: &str, fn_name: &str, ignore: &mut Vec<String>) -> TokenStream {
101    let processed_html = html
102        .replace("\\\"", "\"")
103        .replace("\\n", "\n")
104        .trim_matches('"')
105        .to_string();
106
107    let elements = load_mapper_map(&processed_html)
108        .unwrap_or_else(|_| panic!("Failed to load html: {}", processed_html));
109
110    let (_, element) = elements
111        .into_iter()
112        .next()
113        .unwrap_or_else(|| panic!("HTML not found for function: {}", fn_name));
114
115    parse_html_node(vec![element], ignore, fn_name)
116}
117
118/// Parses HTML nodes into Rust code
119fn parse_html_node(elements: Vec<Element>, ignore: &mut Vec<String>, fn_name: &str) -> TokenStream {
120    let mut methods = quote!();
121    let fn_impl = parse_elements(&elements, &mut methods, ignore, fn_name);
122    quote! { #methods #fn_impl }
123}
124
125/// Main parsing function that converts elements to Rust code using AST nodes
126fn parse_elements(
127    elements: &[Element],
128    methods: &mut TokenStream,
129    ignore: &mut Vec<String>,
130    fn_name: &str,
131) -> TokenStream {
132    let mut body = quote! {};
133
134    // Create a context object that will be passed to node generators
135    let mut context = NodeContext {
136        methods,
137        fn_name,
138        child_parser: parse_elements,
139    };
140
141    for element in elements {
142        match element.tag.as_str() {
143            "" => {
144                // Text node, handle directly here
145                handle_text_element(element, &mut body, ignore);
146            }
147            MAPPER_TAG => {
148                let node = MapperTagNode::from_element(element);
149                body = node.generate_tokens(&mut context, ignore);
150            }
151            SQL_TAG => {
152                let node = SqlTagNode::from_element(element);
153                let code = node.generate_tokens(&mut context, ignore);
154                body = quote! { #body #code };
155            }
156            INCLUDE_TAG => {
157                // Include tags should be processed earlier in include_replace
158                // If we encounter one here, just parse its children
159                let node = IncludeTagNode::from_element(element);
160                let code = node.generate_tokens(&mut context, ignore);
161                body = quote! { #body #code };
162            }
163            CONTINUE_TAG => {
164                let node = ContinueTagNode::from_element(element);
165                let code = node.generate_tokens(&mut context, ignore);
166                body = quote! { #body #code };
167            }
168            BREAK_TAG => {
169                let node = BreakTagNode::from_element(element);
170                let code = node.generate_tokens(&mut context, ignore);
171                body = quote! { #body #code };
172            }
173            IF_TAG => {
174                let node = IfTagNode::from_element(element);
175                let code = node.generate_tokens(&mut context, ignore);
176                body = quote! { #body #code };
177            }
178            TRIM_TAG => {
179                let node = TrimTagNode::from_element(element);
180                let code = node.generate_tokens(&mut context, ignore);
181                body = quote! { #body #code };
182            }
183            BIND_TAG => {
184                let node = BindTagNode::from_element(element);
185                let code = node.generate_tokens(&mut context, ignore);
186                body = quote! { #body #code };
187            }
188            WHERE_TAG => {
189                let node = WhereTagNode::from_element(element);
190                let code = node.generate_tokens(&mut context, ignore);
191                body = quote! { #body #code };
192            }
193            CHOOSE_TAG => {
194                let node = ChooseTagNode::from_element(element);
195                let code = node.generate_tokens(&mut context, ignore);
196                body = quote! { #body #code };
197            }
198            FOREACH_TAG => {
199                let node = ForeachTagNode::from_element(element);
200                let code = node.generate_tokens(&mut context, ignore);
201                body = quote! { #body #code };
202            }
203            SET_TAG => {
204                let node = SetTagNode::from_element(element);
205                let code = node.generate_tokens(&mut context, ignore);
206                body = quote! { #body #code };
207            }
208            SELECT_TAG => {
209                let node = SelectTagNode::from_element(element);
210                let code = node.generate_tokens(&mut context, ignore);
211                body = quote! { #body #code };
212            }
213            UPDATE_TAG => {
214                let node = UpdateTagNode::from_element(element);
215                let code = node.generate_tokens(&mut context, ignore);
216                body = quote! { #body #code };
217            }
218            INSERT_TAG => {
219                let node = InsertTagNode::from_element(element);
220                let code = node.generate_tokens(&mut context, ignore);
221                body = quote! { #body #code };
222            }
223            DELETE_TAG => {
224                let node = DeleteTagNode::from_element(element);
225                let code = node.generate_tokens(&mut context, ignore);
226                body = quote! { #body #code };
227            }
228            WHEN_TAG => {}
229            OTHERWISE_TAG => {}
230            _ => {}
231        }
232    }
233
234    body
235}
236
237/// Handles plain text elements
238fn handle_text_element(element: &Element, body: &mut TokenStream, ignore: &mut Vec<String>) {
239    let mut string_data = remove_extra(&element.data);
240    let convert_list = find_convert_string(&string_data);
241
242    let mut formats_value = quote! {};
243    let mut replace_num = 0;
244
245    for (k, v) in convert_list {
246        let method_impl = crate::codegen::func::impl_fn(
247            &body.to_string(),
248            "",
249            &format!("\"{}\"", k),
250            false,
251            ignore,
252        );
253
254        if v.starts_with('#') {
255            string_data = string_data.replacen(&v, "?", 1);
256            *body = quote! {
257                #body
258                args.push(rbs::value(#method_impl).unwrap_or_default());
259            };
260        } else {
261            string_data = string_data.replacen(&v, "{}", 1);
262            if !formats_value.to_string().trim().ends_with(',') {
263                formats_value = quote!(#formats_value,);
264            }
265            formats_value = quote!(#formats_value &#method_impl.string());
266            replace_num += 1;
267        }
268    }
269
270    if !string_data.is_empty() {
271        *body = if replace_num == 0 {
272            quote! { #body rbatis_codegen::codegen::string_util::concat_str(&mut sql, #string_data); }
273        } else {
274            quote! { #body rbatis_codegen::codegen::string_util::concat_str(&mut sql, &format!(#string_data #formats_value)); }
275        };
276    }
277}
278
279/// Cleans up text content by removing extra characters
280fn remove_extra(text: &str) -> String {
281    let text = text.trim().replace("\\r", "");
282    let lines: Vec<&str> = text.split('\n').collect();
283
284    let mut data = String::with_capacity(text.len());
285    for (i, line) in lines.iter().enumerate() {
286        let mut line = line.trim();
287        line = line.trim_start_matches('`').trim_end_matches('`');
288
289        let list: Vec<&str> = line.split("``").collect();
290        let mut text = String::with_capacity(line.len());
291        for s in list {
292            concat_str(&mut text, s);
293        }
294        data.push_str(&text);
295        if i + 1 < lines.len() {
296            data.push('\n');
297        }
298    }
299
300    data
301}
302
303/// Implements HTML SQL function
304pub fn impl_fn_html(m: &ItemFn, args: &ParseArgs) -> MacroTokenStream {
305    let fn_name = m.sig.ident.to_string();
306
307    if args.sqls.is_empty() {
308        panic!(
309            "[rbatis-codegen] #[html_sql()] must have html_data, for example: {}",
310            stringify!(#[html_sql(r#"<select id="select_by_condition">`select * from biz_activity</select>"#)])
311        );
312    }
313
314    let html_data = args.sqls[0].to_token_stream().to_string();
315    parse_html(&html_data, &fn_name, &mut vec![]).into()
316}