rbatis_codegen/codegen/
parser_html.rs

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