simpletemplate/
lib.rs

1//! Render html templates.
2//!
3//! # Examples
4//!
5//! ```
6//! // variables
7//! let data = json!({ "foo": "bar" });
8//! let template = r"{{ foo }}";
9//! let expected = "bar";
10//! assert_eq!(render(template, data), expected);
11//! ```
12//! 
13//! ```
14//! // logic statements
15//! let data = json!({ "foo": "bar" });
16//! let template = r"{{ if foo }}{{ foo }}{{ else }}foo is not set{{ endif }}";
17//! let expected = "bar";
18//! assert_eq!(render(template, data), expected);
19//! ```
20//! 
21//! ```
22//! let data = json!({ "foo": null });
23//! let template = r"{{ if foo }}{{ foo }}{{ else }}foo is not set{{ endif }}";
24//! let expected = "foo is not set";
25//! assert_eq!(render(template, data), expected);
26//! ```
27//! 
28//! ```
29//! // for loop
30//! let data = json!({ "items": ["a", "b", "c"] });
31//! let template = r"{{ for item in items }}{{ item }}{{ endfor }}";
32//! let expected = "abc";
33//! assert_eq!(render(template, data), expected);
34//! ```
35//! 
36//! ```
37//! let data = json!({ "items": [] });
38//! let template = r"{{ for item in items }}{{ item }}{{ endfor }}";
39//! let expected = "";
40//! assert_eq!(render(template, data), expected);
41//! ```
42//! 
43//! ```
44//! // accessing by index
45//! let data = json!({ "items": ["a", "b", "c"] });
46//! let template = r"{{ items[0] }}";
47//! let expected = "a";
48//! assert_eq!(render(template, data), expected);
49//! ```
50//! 
51//! ```
52//! let data = json!({ "items": ["a", "b", "c"] });
53//! let template = r"{{ items[1] }}";
54//! let expected = "b";
55//! assert_eq!(render(template, data), expected);
56//! ```
57//! 
58//! ```
59//! let data = json!({ "items": ["a", "b", "c"] });
60//! let template = r"{{ items[2] }}";
61//! let expected = "c";
62//! assert_eq!(render(template, data), expected);
63//! ```
64//! 
65//! ```
66//! // combining logic
67//! let data = json!({
68//! "foo": "bar",
69//! "items": ["a", "b", "c"],
70//! "show_items": true,
71//! "show_foo": false,
72//! });
73//! let template = r"{{ if show_items }}{{ for item in items }}{{ item }}{{ endfor }}{{ endif }}{{ if show_foo }}{{ foo }}{{ endif }}";
74//! let expected = "abc";
75//! assert_eq!(render(template, data), expected);
76//! ```
77//! 
78//! ```
79//! let data = json!({
80//! "foo": "bar",
81//! "items": ["a", "b", "c"],
82//! "show_items": true,
83//! "show_foo": false,
84//! });
85//! let template = r"{{ if show_items }}{{ for item in items }}{{ item }}{{ endfor }}{{ endif }}{{ if show_foo }}{{ foo }}{{ else }}foo is not set{{ endif }}";
86//! let expected = "abcfoo is not set";
87//! assert_eq!(render(template, data), expected);
88//! ```
89//! 
90//! ```
91//! // blank template
92//! let data = json!({});
93//! let template = "";
94//! let expected = "";
95//! assert_eq!(render(template, data), expected);
96//! ```
97//! 
98//! ```
99//! // invalid variable
100//! let data = json!({ "foo": "bar" });
101//! let template = "{{ baz }}";
102//! let expected = "null";
103//! assert_eq!(render(template, data), expected);
104//! ```
105//! 
106//! ```
107//! // invalid index
108//! let data = json!({ "items": ["a", "b", "c"] });
109//! let template = "{{ items[5] }}";
110//! let expected = "null";
111//! assert_eq!(render(template, data), expected);
112//! ```
113//! 
114//! ```
115//! // invalid statement
116//! let data = json!({});
117//! let template = "{{ if foo }}{{ endif }}";
118//! let expected = "";
119//! assert_eq!(render(template, data), expected);
120//! ```
121
122pub use serde_json::{Value};
123pub use regex::Regex;
124pub use string_join::display::Join;
125
126pub fn render(content: &str, data: Value) -> String {
127    // handle for loop
128    let re = Regex::new(r"\{\{ for (\w+) in (\w+) \}\}([\s\S]*?)\{\{ endfor \}\}").unwrap();
129    let replaced = re.replace_all(content, |caps: &regex::Captures| {
130        let loop_variable = &caps[1];
131        let loop_variable_formatted = "".join(["{{ ", loop_variable, " }}"]);
132        let loop_variable = loop_variable_formatted.as_str();
133
134        let loop_iterable = &caps[2];
135        let loop_body = &caps[3];
136
137        let iterable = &data[loop_iterable];
138        if !iterable.is_array() {
139            return "".to_string()
140        }
141
142        let mut result = "".to_string();
143        for (index, value) in iterable.as_array().unwrap().iter().enumerate() {
144            let mut loop_body_replaced = loop_body.trim_start_matches('\n').to_string();
145            let val = get_value_string(value);
146            loop_body_replaced = loop_body_replaced.replace(loop_variable, &val);
147            loop_body_replaced = loop_body_replaced.replace("{{ index }}", &index.to_string());
148            result.push_str(&loop_body_replaced);
149        }
150        result
151    }); 
152
153    // handle if statements
154    let re = Regex::new(r"\{\{ if (\w+) \}\}\s*([\s\S]*?)\s*(?:\{\{ else \}\}\s*([\s\S]*?)\s*)?\{\{ endif \}\}").unwrap();
155    let replaced = re.replace_all(&replaced, |caps: &regex::Captures| {
156        let matched_group_count = (1..caps.len()).filter(|i| caps.get(*i).is_some()).count();
157        let condition = &caps[1];
158        let if_body = &caps[2];
159        let else_body = if matched_group_count > 2 {
160            &caps[3]
161        } else {
162            ""
163        };
164
165        let value = &data[condition];
166        if value.is_null() || value == "false" || value == false {
167            else_body.to_string()
168        } else {
169            if_body.to_string()
170        }
171    });
172
173
174    // handle variables
175    let re = Regex::new(r"\{\{ (\w+) \}\}").unwrap();
176    let replaced = re.replace_all(&replaced, |caps: &regex::Captures| {
177        let key = &caps[1];
178        let value = &data[key];
179        if value.is_array() {
180            // Join the array values into a single string, separated by commas
181            value.as_array()
182                .unwrap()
183                .iter()
184                .map(|v| get_value_string(v))
185                .collect::<Vec<_>>()
186                .join(", ")
187        } else {
188            get_value_string(value)
189        }
190    }); 
191
192    // handle indexing
193    let re = Regex::new(r"\{\{ (\w+)\[(\d+)\] \}\}").unwrap();
194    let replaced = re.replace_all(&replaced, |caps: &regex::Captures| {
195        let array_name = &caps[1];
196        let index = caps[2].parse::<usize>().unwrap();
197        let value = &data[array_name][index];
198        get_value_string(value)        
199    });
200
201    replaced.to_string()
202}
203
204fn get_value_string(value: &Value) -> String {
205    if value.is_string() {
206        value.as_str().unwrap().to_string()
207    } else {
208        value.to_string()
209    }
210}