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