oxidite_template/
renderer.rs1use crate::{Context, TemplateNode, TemplateError, Result, filters::Filters, TemplateEngine, Template};
2use serde_json::Value;
3use std::collections::HashMap;
4
5pub struct Renderer<'a> {
7 context: &'a Context,
8 filters: Filters,
9 engine: Option<&'a TemplateEngine>,
10 blocks: HashMap<String, Vec<TemplateNode>>,
11}
12
13impl<'a> Renderer<'a> {
14 pub fn new(context: &'a Context, engine: Option<&'a TemplateEngine>) -> Self {
15 Self {
16 context,
17 filters: Filters::new(),
18 engine,
19 blocks: HashMap::new(),
20 }
21 }
22
23 pub fn render(&mut self, template: &Template) -> Result<String> {
24 let extends_node = template.parsed.iter().find(|node| {
26 match node {
27 TemplateNode::Text(t) => !t.trim().is_empty(),
28 _ => true,
29 }
30 });
31
32 if let Some(TemplateNode::Extends(parent_name)) = extends_node {
33 for node in &template.parsed {
36 if let TemplateNode::Block { name, body } = node {
37 self.blocks.entry(name.clone()).or_insert(body.clone());
44 }
45 }
46
47 if let Some(engine) = self.engine {
48 let parent = engine.get_template(parent_name)
49 .ok_or_else(|| TemplateError::RenderError(format!("Parent template not found: {}", parent_name)))?;
50 return self.render(parent);
51 } else {
52 return Err(TemplateError::RenderError("Extends used without TemplateEngine".to_string()));
53 }
54 }
55
56 self.render_nodes(&template.parsed)
57 }
58
59 fn render_nodes(&mut self, nodes: &[TemplateNode]) -> Result<String> {
60 let mut output = String::new();
61
62 for node in nodes {
63 match node {
64 TemplateNode::Text(text) => {
65 output.push_str(text);
66 }
67 TemplateNode::Variable { name, filters } => {
68 let value = self.render_variable(name, filters)?;
69 output.push_str(&value);
70 }
71 TemplateNode::If { condition, then_branch, else_branch } => {
72 let value = self.render_if(condition, then_branch, else_branch)?;
73 output.push_str(&value);
74 }
75 TemplateNode::For { item, iterable, body } => {
76 let value = self.render_for(item, iterable, body)?;
77 output.push_str(&value);
78 }
79 TemplateNode::Block { name, body } => {
80 if let Some(override_body) = self.blocks.get(name).cloned() {
82 let nodes = override_body;
84 output.push_str(&self.render_nodes(&nodes)?);
85 } else {
86 output.push_str(&self.render_nodes(body)?);
87 }
88 }
89 TemplateNode::Extends(_) => {
90 }
94 TemplateNode::Include(template_name) => {
95 if let Some(engine) = self.engine {
96 let template = engine.get_template(template_name)
97 .ok_or_else(|| TemplateError::RenderError(format!("Included template not found: {}", template_name)))?;
98
99 let mut sub_renderer = Renderer::new(self.context, self.engine);
105 output.push_str(&sub_renderer.render(template)?);
106 } else {
107 return Err(TemplateError::RenderError("Include used without TemplateEngine".to_string()));
108 }
109 }
110 }
111 }
112
113 Ok(output)
114 }
115
116 fn render_variable(&self, name: &str, filter_names: &[String]) -> Result<String> {
117 let value = self.context.get(name)
118 .ok_or_else(|| TemplateError::VariableNotFound(name.to_string()))?;
119
120 let mut result = self.value_to_string(value);
121
122 for filter_name in filter_names {
124 result = self.filters.apply(filter_name, &result)?;
125 }
126
127 result = html_escape(&result);
129
130 Ok(result)
131 }
132
133 fn render_if(&mut self, condition: &str, then_branch: &[TemplateNode], else_branch: &Option<Vec<TemplateNode>>) -> Result<String> {
134 let is_truthy = self.evaluate_condition(condition);
136
137 if is_truthy {
138 self.render_nodes(then_branch)
139 } else if let Some(else_nodes) = else_branch {
140 self.render_nodes(else_nodes)
141 } else {
142 Ok(String::new())
143 }
144 }
145
146 fn render_for(&mut self, item: &str, iterable: &str, body: &[TemplateNode]) -> Result<String> {
147 let array = self.context.get(iterable)
148 .ok_or_else(|| TemplateError::VariableNotFound(iterable.to_string()))?;
149
150 let mut output = String::new();
151
152 if let Value::Array(items) = array {
153 for item_value in items {
154 let mut loop_context = self.context.clone();
156 loop_context.data.insert(item.to_string(), item_value.clone());
157
158 let mut renderer = Renderer::new(&loop_context, self.engine);
159 renderer.blocks = self.blocks.clone();
163 output.push_str(&renderer.render_nodes(body)?);
164 }
165 }
166
167 Ok(output)
168 }
169
170 fn evaluate_condition(&self, condition: &str) -> bool {
171 if let Some(value) = self.context.get(condition) {
172 match value {
173 Value::Bool(b) => *b,
174 Value::Null => false,
175 Value::String(s) => !s.is_empty(),
176 Value::Number(_) => true,
177 Value::Array(a) => !a.is_empty(),
178 Value::Object(o) => !o.is_empty(),
179 }
180 } else {
181 false
182 }
183 }
184
185 fn value_to_string(&self, value: &Value) -> String {
186 match value {
187 Value::String(s) => s.clone(),
188 Value::Number(n) => n.to_string(),
189 Value::Bool(b) => b.to_string(),
190 Value::Null => String::new(),
191 _ => serde_json::to_string(value).unwrap_or_default(),
192 }
193 }
194}
195
196fn html_escape(s: &str) -> String {
198 s.replace('&', "&")
199 .replace('<', "<")
200 .replace('>', ">")
201 .replace('"', """)
202 .replace('\'', "'")
203}