handlebars/
render.rs

1use std::borrow::{Borrow, Cow};
2use std::collections::{BTreeMap, VecDeque};
3use std::fmt;
4use std::rc::Rc;
5
6use serde_json::value::Value as Json;
7
8use crate::block::BlockContext;
9use crate::context::Context;
10use crate::error::RenderError;
11use crate::helpers::HelperDef;
12use crate::json::path::Path;
13use crate::json::value::{JsonRender, PathAndJson, ScopedJson};
14use crate::output::{Output, StringOutput};
15use crate::registry::Registry;
16use crate::support;
17use crate::support::str::newline_matcher;
18use crate::template::TemplateElement::{
19    DecoratorBlock, DecoratorExpression, Expression, HelperBlock, HtmlExpression, PartialBlock,
20    PartialExpression, RawString,
21};
22use crate::template::{
23    BlockParam, DecoratorTemplate, HelperTemplate, Parameter, Template, TemplateElement,
24    TemplateMapping,
25};
26use crate::{partial, RenderErrorReason};
27
28const HELPER_MISSING: &str = "helperMissing";
29const BLOCK_HELPER_MISSING: &str = "blockHelperMissing";
30
31/// The context of a render call
32///
33/// This context stores information of a render and a writer where generated
34/// content is written to.
35///
36#[derive(Clone)]
37pub struct RenderContext<'reg: 'rc, 'rc> {
38    dev_mode_templates: Option<&'rc BTreeMap<String, Cow<'rc, Template>>>,
39
40    blocks: VecDeque<BlockContext<'rc>>,
41
42    // copy-on-write context
43    modified_context: Option<Rc<Context>>,
44
45    partials: BTreeMap<String, &'rc Template>,
46    partial_block_stack: VecDeque<&'rc Template>,
47    partial_block_depth: isize,
48    local_helpers: BTreeMap<String, Rc<dyn HelperDef + Send + Sync + 'rc>>,
49    /// current template name
50    current_template: Option<&'rc String>,
51    /// root template name
52    root_template: Option<&'reg String>,
53    disable_escape: bool,
54
55    // Indicates whether the previous text that we rendered ended on a newline.
56    // This is necessary to make indenting decisions after the end of partials.
57    trailing_newline: bool,
58
59    // This should be set to true whenever any output is written.
60    // We need this to detect empty partials/helpers for indenting decisions.
61    content_produced: bool,
62
63    // The next text that we render should indent itself.
64    indent_before_write: bool,
65    indent_string: Option<Cow<'rc, str>>,
66}
67
68impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
69    /// Create a render context
70    pub fn new(root_template: Option<&'reg String>) -> RenderContext<'reg, 'rc> {
71        let mut blocks = VecDeque::with_capacity(5);
72        blocks.push_front(BlockContext::new());
73
74        let modified_context = None;
75        RenderContext {
76            partials: BTreeMap::new(),
77            partial_block_stack: VecDeque::new(),
78            partial_block_depth: 0,
79            local_helpers: BTreeMap::new(),
80            current_template: None,
81            root_template,
82            disable_escape: false,
83            trailing_newline: false,
84            content_produced: false,
85            indent_before_write: false,
86            indent_string: None,
87            blocks,
88            modified_context,
89            dev_mode_templates: None,
90        }
91    }
92
93    /// Push a block context into render context stack. This is typically
94    /// called when you entering a block scope.
95    pub fn push_block(&mut self, block: BlockContext<'rc>) {
96        self.blocks.push_front(block);
97    }
98
99    /// Pop and drop current block context.
100    /// This is typically called when leaving a block scope.
101    pub fn pop_block(&mut self) {
102        self.blocks.pop_front();
103    }
104
105    /// Remove all blocks
106    pub(crate) fn clear_blocks(&mut self) {
107        self.blocks.clear();
108    }
109
110    /// Borrow a reference to current block context
111    pub fn block(&self) -> Option<&BlockContext<'rc>> {
112        self.blocks.front()
113    }
114
115    /// Borrow a mutable reference to current block context in order to
116    /// modify some data.
117    pub fn block_mut(&mut self) -> Option<&mut BlockContext<'rc>> {
118        self.blocks.front_mut()
119    }
120
121    /// Replace blocks hold by render context and return the replaced blocks
122    pub fn replace_blocks(
123        &mut self,
124        blocks: VecDeque<BlockContext<'rc>>,
125    ) -> VecDeque<BlockContext<'rc>> {
126        std::mem::replace(&mut self.blocks, blocks)
127    }
128
129    /// Get the modified context data if any
130    pub fn context(&self) -> Option<Rc<Context>> {
131        self.modified_context.clone()
132    }
133
134    /// Set new context data into the render process.
135    /// This is typically called in decorators where user can modify
136    /// the data they were rendering.
137    pub fn set_context(&mut self, ctx: Context) {
138        self.modified_context = Some(Rc::new(ctx));
139    }
140
141    /// Evaluate a Json path in current scope.
142    ///
143    /// Typically you don't need to evaluate it by yourself.
144    /// The Helper and Decorator API will provide your evaluated value of
145    /// their parameters and hash data.
146    pub fn evaluate(
147        &self,
148        context: &'rc Context,
149        relative_path: &str,
150    ) -> Result<ScopedJson<'rc>, RenderError> {
151        let path = Path::parse(relative_path)?;
152        self.evaluate2(context, &path)
153    }
154
155    pub(crate) fn evaluate2(
156        &self,
157        context: &'rc Context,
158        path: &Path,
159    ) -> Result<ScopedJson<'rc>, RenderError> {
160        match path {
161            Path::Local((level, name, _)) => Ok(self
162                .get_local_var(*level, name)
163                .map_or_else(|| ScopedJson::Missing, |v| ScopedJson::Derived(v.clone()))),
164            Path::Relative((segs, _)) => context.navigate(segs, &self.blocks),
165        }
166    }
167
168    /// Get registered partial in this render context
169    pub fn get_partial(&self, name: &str) -> Option<&'rc Template> {
170        if name == partial::PARTIAL_BLOCK {
171            return self
172                .partial_block_stack
173                .get(self.partial_block_depth as usize)
174                .copied();
175        }
176        self.partials.get(name).copied()
177    }
178
179    /// Register a partial for this context
180    pub fn set_partial(&mut self, name: String, partial: &'rc Template) {
181        self.partials.insert(name, partial);
182    }
183
184    pub(crate) fn push_partial_block(&mut self, partial: &'rc Template) {
185        self.partial_block_stack.push_front(partial);
186    }
187
188    pub(crate) fn pop_partial_block(&mut self) {
189        self.partial_block_stack.pop_front();
190    }
191
192    pub(crate) fn inc_partial_block_depth(&mut self) {
193        self.partial_block_depth += 1;
194    }
195
196    pub(crate) fn dec_partial_block_depth(&mut self) {
197        let depth = &mut self.partial_block_depth;
198        if *depth > 0 {
199            *depth -= 1;
200        }
201    }
202
203    pub(crate) fn set_indent_string(&mut self, indent: Option<Cow<'rc, str>>) {
204        self.indent_string = indent;
205    }
206
207    #[inline]
208    pub(crate) fn get_indent_string(&self) -> Option<&Cow<'rc, str>> {
209        self.indent_string.as_ref()
210    }
211
212    pub(crate) fn get_dev_mode_template(&self, name: &str) -> Option<&'rc Template> {
213        self.dev_mode_templates
214            .and_then(|dmt| dmt.get(name).map(|t| &**t))
215    }
216
217    pub(crate) fn set_dev_mode_templates(
218        &mut self,
219        t: Option<&'rc BTreeMap<String, Cow<'rc, Template>>>,
220    ) {
221        self.dev_mode_templates = t;
222    }
223
224    /// Remove a registered partial
225    pub fn remove_partial(&mut self, name: &str) {
226        self.partials.remove(name);
227    }
228
229    fn get_local_var(&self, level: usize, name: &str) -> Option<&Json> {
230        self.blocks
231            .get(level)
232            .and_then(|blk| blk.get_local_var(name))
233    }
234
235    /// Test if given template name is current template.
236    pub fn is_current_template(&self, p: &str) -> bool {
237        self.current_template.is_some_and(|s| s == p)
238    }
239
240    /// Register a helper in this render context.
241    /// This is a feature provided by Decorator where you can create
242    /// temporary helpers.
243    pub fn register_local_helper(
244        &mut self,
245        name: &str,
246        def: Box<dyn HelperDef + Send + Sync + 'rc>,
247    ) {
248        self.local_helpers.insert(name.to_string(), def.into());
249    }
250
251    /// Remove a helper from render context
252    pub fn unregister_local_helper(&mut self, name: &str) {
253        self.local_helpers.remove(name);
254    }
255
256    /// Attempt to get a helper from current render context.
257    pub fn get_local_helper(&self, name: &str) -> Option<Rc<dyn HelperDef + Send + Sync + 'rc>> {
258        self.local_helpers.get(name).cloned()
259    }
260
261    #[inline]
262    fn has_local_helper(&self, name: &str) -> bool {
263        self.local_helpers.contains_key(name)
264    }
265
266    /// Returns the current template name.
267    /// Note that the name can be vary from root template when you are rendering
268    /// from partials.
269    pub fn get_current_template_name(&self) -> Option<&'rc String> {
270        self.current_template
271    }
272
273    /// Set the current template name.
274    pub fn set_current_template_name(&mut self, name: Option<&'rc String>) {
275        self.current_template = name;
276    }
277
278    /// Get root template name if any.
279    /// This is the template name that you call `render` from `Handlebars`.
280    pub fn get_root_template_name(&self) -> Option<&'reg String> {
281        self.root_template
282    }
283
284    /// Get the escape toggle
285    pub fn is_disable_escape(&self) -> bool {
286        self.disable_escape
287    }
288
289    /// Set the escape toggle.
290    /// When toggle is on, `escape_fn` will be called when rendering.
291    pub fn set_disable_escape(&mut self, disable: bool) {
292        self.disable_escape = disable;
293    }
294
295    #[inline]
296    pub fn set_trailing_newline(&mut self, trailing_newline: bool) {
297        self.trailing_newline = trailing_newline;
298    }
299
300    #[inline]
301    pub fn get_trailine_newline(&self) -> bool {
302        self.trailing_newline
303    }
304
305    #[inline]
306    pub fn set_content_produced(&mut self, content_produced: bool) {
307        self.content_produced = content_produced;
308    }
309
310    #[inline]
311    pub fn get_content_produced(&self) -> bool {
312        self.content_produced
313    }
314
315    #[inline]
316    pub fn set_indent_before_write(&mut self, indent_before_write: bool) {
317        self.indent_before_write = indent_before_write;
318    }
319
320    #[inline]
321    pub fn get_indent_before_write(&self) -> bool {
322        self.indent_before_write
323    }
324}
325
326impl fmt::Debug for RenderContext<'_, '_> {
327    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
328        f.debug_struct("RenderContextInner")
329            .field("dev_mode_templates", &self.dev_mode_templates)
330            .field("blocks", &self.blocks)
331            .field("modified_context", &self.modified_context)
332            .field("partials", &self.partials)
333            .field("partial_block_stack", &self.partial_block_stack)
334            .field("partial_block_depth", &self.partial_block_depth)
335            .field("root_template", &self.root_template)
336            .field("current_template", &self.current_template)
337            .field("disable_escape", &self.disable_escape)
338            .finish()
339    }
340}
341
342/// Render-time Helper data when using in a helper definition
343#[derive(Debug, Clone)]
344pub struct Helper<'rc> {
345    name: Cow<'rc, str>,
346    params: Vec<PathAndJson<'rc>>,
347    hash: BTreeMap<&'rc str, PathAndJson<'rc>>,
348    template: Option<&'rc Template>,
349    inverse: Option<&'rc Template>,
350    block_param: Option<&'rc BlockParam>,
351    block: bool,
352}
353
354impl<'reg: 'rc, 'rc> Helper<'rc> {
355    fn try_from_template(
356        ht: &'rc HelperTemplate,
357        registry: &'reg Registry<'reg>,
358        context: &'rc Context,
359        render_context: &mut RenderContext<'reg, 'rc>,
360    ) -> Result<Helper<'rc>, RenderError> {
361        let name = ht.name.expand_as_name(registry, context, render_context)?;
362        let mut pv = Vec::with_capacity(ht.params.len());
363        for p in &ht.params {
364            let r = p.expand(registry, context, render_context)?;
365            pv.push(r);
366        }
367
368        let mut hm = BTreeMap::new();
369        for (k, p) in &ht.hash {
370            let r = p.expand(registry, context, render_context)?;
371            hm.insert(k.as_ref(), r);
372        }
373
374        Ok(Helper {
375            name,
376            params: pv,
377            hash: hm,
378            template: ht.template.as_ref(),
379            inverse: ht.inverse.as_ref(),
380            block_param: ht.block_param.as_ref(),
381            block: ht.block,
382        })
383    }
384
385    /// Returns helper name
386    pub fn name(&self) -> &str {
387        &self.name
388    }
389
390    /// Returns all helper params, resolved within the context
391    pub fn params(&self) -> &Vec<PathAndJson<'rc>> {
392        &self.params
393    }
394
395    /// Returns nth helper param, resolved within the context.
396    ///
397    /// ## Example
398    ///
399    /// To get the first param in `{{my_helper abc}}` or `{{my_helper 2}}`,
400    /// use `h.param(0)` in helper definition.
401    /// Variable `abc` is auto resolved in current context.
402    ///
403    /// ```
404    /// use handlebars::*;
405    ///
406    /// fn my_helper(h: &Helper, rc: &mut RenderContext) -> Result<(), RenderError> {
407    ///     let v = h.param(0).map(|v| v.value())
408    ///         .ok_or(RenderErrorReason::ParamNotFoundForIndex("myhelper", 0));
409    ///     // ..
410    ///     Ok(())
411    /// }
412    /// ```
413    pub fn param(&self, idx: usize) -> Option<&PathAndJson<'rc>> {
414        self.params.get(idx)
415    }
416
417    /// Returns hash, resolved within the context
418    pub fn hash(&self) -> &BTreeMap<&'rc str, PathAndJson<'rc>> {
419        &self.hash
420    }
421
422    /// Return hash value of a given key, resolved within the context
423    ///
424    /// ## Example
425    ///
426    /// To get the first param in `{{my_helper v=abc}}` or `{{my_helper v=2}}`,
427    /// use `h.hash_get("v")` in helper definition.
428    /// Variable `abc` is auto resolved in current context.
429    ///
430    /// ```
431    /// use handlebars::*;
432    ///
433    /// fn my_helper(h: &Helper, rc: &mut RenderContext) -> Result<(), RenderError> {
434    ///     let v = h.hash_get("v").map(|v| v.value())
435    ///         .ok_or(RenderErrorReason::ParamNotFoundForIndex("my_helper", 0));
436    ///     // ..
437    ///     Ok(())
438    /// }
439    /// ```
440    pub fn hash_get(&self, key: &str) -> Option<&PathAndJson<'rc>> {
441        self.hash.get(key)
442    }
443
444    /// Returns the default inner template if the helper is a block helper.
445    ///
446    /// Typically you will render the template via: `template.render(registry, render_context)`
447    ///
448    pub fn template(&self) -> Option<&'rc Template> {
449        self.template
450    }
451
452    /// Returns the template of `else` branch if any
453    pub fn inverse(&self) -> Option<&'rc Template> {
454        self.inverse
455    }
456
457    /// Returns if the helper is a block one `{{#helper}}{{/helper}}` or not `{{helper 123}}`
458    pub fn is_block(&self) -> bool {
459        self.block
460    }
461
462    /// Returns if the helper has either a block param or block param pair
463    pub fn has_block_param(&self) -> bool {
464        self.block_param.is_some()
465    }
466
467    /// Returns block param if any
468    pub fn block_param(&self) -> Option<&'rc str> {
469        if let Some(&BlockParam::Single(Parameter::Name(ref s))) = self.block_param {
470            Some(s)
471        } else {
472            None
473        }
474    }
475
476    /// Return block param pair (for example |key, val|) if any
477    pub fn block_param_pair(&self) -> Option<(&'rc str, &'rc str)> {
478        if let Some(&BlockParam::Pair((Parameter::Name(ref s1), Parameter::Name(ref s2)))) =
479            self.block_param
480        {
481            Some((s1, s2))
482        } else {
483            None
484        }
485    }
486}
487
488/// Render-time Decorator data when using in a decorator definition
489#[derive(Debug)]
490pub struct Decorator<'rc> {
491    name: Cow<'rc, str>,
492    params: Vec<PathAndJson<'rc>>,
493    hash: BTreeMap<&'rc str, PathAndJson<'rc>>,
494    template: Option<&'rc Template>,
495    indent: Option<Cow<'rc, str>>,
496}
497
498impl<'reg: 'rc, 'rc> Decorator<'rc> {
499    fn try_from_template(
500        dt: &'rc DecoratorTemplate,
501        registry: &'reg Registry<'reg>,
502        context: &'rc Context,
503        render_context: &mut RenderContext<'reg, 'rc>,
504    ) -> Result<Decorator<'rc>, RenderError> {
505        let name = dt.name.expand_as_name(registry, context, render_context)?;
506
507        let mut pv = Vec::with_capacity(dt.params.len());
508        for p in &dt.params {
509            let r = p.expand(registry, context, render_context)?;
510            pv.push(r);
511        }
512
513        let mut hm = BTreeMap::new();
514        for (k, p) in &dt.hash {
515            let r = p.expand(registry, context, render_context)?;
516            hm.insert(k.as_ref(), r);
517        }
518
519        let indent = match (render_context.get_indent_string(), dt.indent.as_ref()) {
520            (None, None) => None,
521            (Some(s), None) => Some(s.clone()),
522            (None, Some(s)) => Some(Cow::Borrowed(&**s)),
523            (Some(s1), Some(s2)) => {
524                let mut res = s1.to_string();
525                res.push_str(s2);
526                Some(Cow::from(res))
527            }
528        };
529
530        Ok(Decorator {
531            name,
532            params: pv,
533            hash: hm,
534            template: dt.template.as_ref(),
535            indent,
536        })
537    }
538
539    /// Returns helper name
540    pub fn name(&self) -> &str {
541        self.name.as_ref()
542    }
543
544    /// Returns all helper params, resolved within the context
545    pub fn params(&self) -> &Vec<PathAndJson<'rc>> {
546        &self.params
547    }
548
549    /// Returns nth helper param, resolved within the context
550    pub fn param(&self, idx: usize) -> Option<&PathAndJson<'rc>> {
551        self.params.get(idx)
552    }
553
554    /// Returns hash, resolved within the context
555    pub fn hash(&self) -> &BTreeMap<&'rc str, PathAndJson<'rc>> {
556        &self.hash
557    }
558
559    /// Return hash value of a given key, resolved within the context
560    pub fn hash_get(&self, key: &str) -> Option<&PathAndJson<'rc>> {
561        self.hash.get(key)
562    }
563
564    /// Returns the default inner template if any
565    pub fn template(&self) -> Option<&'rc Template> {
566        self.template
567    }
568
569    pub fn indent(&self) -> Option<&Cow<'rc, str>> {
570        self.indent.as_ref()
571    }
572}
573
574/// Render trait
575pub trait Renderable {
576    /// render into `RenderContext`'s `writer`
577    fn render<'reg: 'rc, 'rc>(
578        &'rc self,
579        registry: &'reg Registry<'reg>,
580        context: &'rc Context,
581        rc: &mut RenderContext<'reg, 'rc>,
582        out: &mut dyn Output,
583    ) -> Result<(), RenderError>;
584
585    /// render into string
586    fn renders<'reg: 'rc, 'rc>(
587        &'rc self,
588        registry: &'reg Registry<'reg>,
589        ctx: &'rc Context,
590        rc: &mut RenderContext<'reg, 'rc>,
591    ) -> Result<String, RenderError> {
592        let mut so = StringOutput::new();
593        self.render(registry, ctx, rc, &mut so)?;
594        so.into_string()
595            .map_err(|e| RenderErrorReason::from(e).into())
596    }
597}
598
599/// Evaluate decorator
600pub trait Evaluable {
601    fn eval<'reg: 'rc, 'rc>(
602        &'rc self,
603        registry: &'reg Registry<'reg>,
604        context: &'rc Context,
605        rc: &mut RenderContext<'reg, 'rc>,
606    ) -> Result<(), RenderError>;
607}
608
609#[inline]
610fn call_helper_for_value<'reg: 'rc, 'rc>(
611    hd: &dyn HelperDef,
612    ht: &Helper<'rc>,
613    r: &'reg Registry<'reg>,
614    ctx: &'rc Context,
615    rc: &mut RenderContext<'reg, 'rc>,
616) -> Result<PathAndJson<'rc>, RenderError> {
617    match hd.call_inner(ht, r, ctx, rc) {
618        Ok(result) => Ok(PathAndJson::new(None, result)),
619        Err(e) => {
620            if e.is_unimplemented() {
621                // parse value from output
622                let mut so = StringOutput::new();
623
624                // here we don't want subexpression result escaped,
625                // so we temporarily disable it
626                let disable_escape = rc.is_disable_escape();
627                rc.set_disable_escape(true);
628
629                hd.call(ht, r, ctx, rc, &mut so)?;
630                rc.set_disable_escape(disable_escape);
631
632                let string = so.into_string().map_err(RenderError::from)?;
633                Ok(PathAndJson::new(
634                    None,
635                    ScopedJson::Derived(Json::String(string)),
636                ))
637            } else {
638                Err(e)
639            }
640        }
641    }
642}
643
644impl Parameter {
645    pub fn expand_as_name<'reg: 'rc, 'rc>(
646        &'rc self,
647        registry: &'reg Registry<'reg>,
648        ctx: &'rc Context,
649        rc: &mut RenderContext<'reg, 'rc>,
650    ) -> Result<Cow<'rc, str>, RenderError> {
651        match self {
652            Parameter::Name(ref name) => Ok(Cow::Borrowed(name)),
653            Parameter::Path(ref p) => Ok(Cow::Borrowed(p.raw())),
654            Parameter::Subexpression(_) => self
655                .expand(registry, ctx, rc)
656                .map(|v| v.value().render())
657                .map(Cow::Owned),
658            Parameter::Literal(ref j) => Ok(Cow::Owned(j.render())),
659        }
660    }
661
662    pub fn expand<'reg: 'rc, 'rc>(
663        &'rc self,
664        registry: &'reg Registry<'reg>,
665        ctx: &'rc Context,
666        rc: &mut RenderContext<'reg, 'rc>,
667    ) -> Result<PathAndJson<'rc>, RenderError> {
668        match self {
669            Parameter::Name(ref name) => {
670                // FIXME: raise error when expanding with name?
671                Ok(PathAndJson::new(Some(name.to_owned()), ScopedJson::Missing))
672            }
673            Parameter::Path(ref path) => {
674                if let Some(rc_context) = rc.context() {
675                    let result = rc.evaluate2(rc_context.borrow(), path)?;
676                    Ok(PathAndJson::new(
677                        Some(path.raw().to_owned()),
678                        ScopedJson::Derived(result.as_json().clone()),
679                    ))
680                } else {
681                    let result = rc.evaluate2(ctx, path)?;
682                    Ok(PathAndJson::new(Some(path.raw().to_owned()), result))
683                }
684            }
685            Parameter::Literal(ref j) => Ok(PathAndJson::new(None, ScopedJson::Constant(j))),
686            Parameter::Subexpression(ref t) => match *t.as_element() {
687                Expression(ref ht) => {
688                    let name = ht.name.expand_as_name(registry, ctx, rc)?;
689
690                    let h = Helper::try_from_template(ht, registry, ctx, rc)?;
691                    if let Some(ref d) = rc.get_local_helper(&name) {
692                        call_helper_for_value(d.as_ref(), &h, registry, ctx, rc)
693                    } else {
694                        let mut helper = registry.get_or_load_helper(&name)?;
695
696                        if helper.is_none() {
697                            helper = registry.get_or_load_helper(if ht.block {
698                                BLOCK_HELPER_MISSING
699                            } else {
700                                HELPER_MISSING
701                            })?;
702                        }
703
704                        helper
705                            .ok_or_else(|| {
706                                RenderErrorReason::HelperNotFound(name.to_string()).into()
707                            })
708                            .and_then(|d| call_helper_for_value(d.as_ref(), &h, registry, ctx, rc))
709                    }
710                }
711                _ => unreachable!(),
712            },
713        }
714    }
715}
716
717impl Renderable for Template {
718    fn render<'reg: 'rc, 'rc>(
719        &'rc self,
720        registry: &'reg Registry<'reg>,
721        ctx: &'rc Context,
722        rc: &mut RenderContext<'reg, 'rc>,
723        out: &mut dyn Output,
724    ) -> Result<(), RenderError> {
725        rc.set_current_template_name(self.name.as_ref());
726        let iter = self.elements.iter();
727
728        for (idx, t) in iter.enumerate() {
729            t.render(registry, ctx, rc, out).map_err(|mut e| {
730                // add line/col number if the template has mapping data
731                if e.line_no.is_none() {
732                    if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) {
733                        e.line_no = Some(line);
734                        e.column_no = Some(col);
735                    }
736                }
737
738                if e.template_name.is_none() {
739                    e.template_name.clone_from(&self.name);
740                }
741
742                e
743            })?;
744        }
745
746        Ok(())
747    }
748}
749
750impl Evaluable for Template {
751    fn eval<'reg: 'rc, 'rc>(
752        &'rc self,
753        registry: &'reg Registry<'reg>,
754        ctx: &'rc Context,
755        rc: &mut RenderContext<'reg, 'rc>,
756    ) -> Result<(), RenderError> {
757        let iter = self.elements.iter();
758
759        for (idx, t) in iter.enumerate() {
760            t.eval(registry, ctx, rc).map_err(|mut e| {
761                if e.line_no.is_none() {
762                    if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) {
763                        e.line_no = Some(line);
764                        e.column_no = Some(col);
765                    }
766                }
767
768                e.template_name.clone_from(&self.name);
769                e
770            })?;
771        }
772        Ok(())
773    }
774}
775
776fn helper_exists<'reg: 'rc, 'rc>(
777    name: &str,
778    reg: &Registry<'reg>,
779    rc: &RenderContext<'reg, 'rc>,
780) -> bool {
781    rc.has_local_helper(name) || reg.has_helper(name)
782}
783
784#[inline]
785fn render_helper<'reg: 'rc, 'rc>(
786    ht: &'rc HelperTemplate,
787    registry: &'reg Registry<'reg>,
788    ctx: &'rc Context,
789    rc: &mut RenderContext<'reg, 'rc>,
790    out: &mut dyn Output,
791) -> Result<(), RenderError> {
792    let h = Helper::try_from_template(ht, registry, ctx, rc)?;
793    debug!(
794        "Rendering helper: {:?}, params: {:?}, hash: {:?}",
795        h.name(),
796        h.params(),
797        h.hash()
798    );
799    let mut call_indent_aware = |helper_def: &dyn HelperDef, rc: &mut RenderContext<'reg, 'rc>| {
800        let indent_directive_before = rc.get_indent_before_write();
801        let content_produced_before = rc.get_content_produced();
802        rc.set_content_produced(false);
803        rc.set_indent_before_write(
804            indent_directive_before || (ht.indent_before_write && rc.get_trailine_newline()),
805        );
806
807        helper_def.call(&h, registry, ctx, rc, out)?;
808
809        if rc.get_content_produced() {
810            rc.set_indent_before_write(rc.get_trailine_newline());
811        } else {
812            rc.set_content_produced(content_produced_before);
813            rc.set_indent_before_write(indent_directive_before);
814        }
815        Ok(())
816    };
817    if let Some(ref d) = rc.get_local_helper(h.name()) {
818        call_indent_aware(&**d, rc)
819    } else {
820        let mut helper = registry.get_or_load_helper(h.name())?;
821
822        if helper.is_none() {
823            helper = registry.get_or_load_helper(if ht.block {
824                BLOCK_HELPER_MISSING
825            } else {
826                HELPER_MISSING
827            })?;
828        }
829
830        helper
831            .ok_or_else(|| RenderErrorReason::HelperNotFound(h.name().to_owned()).into())
832            .and_then(|d| call_indent_aware(&*d, rc))
833    }
834}
835
836pub(crate) fn do_escape(r: &Registry<'_>, rc: &RenderContext<'_, '_>, content: String) -> String {
837    if !rc.is_disable_escape() {
838        r.get_escape_fn()(&content)
839    } else {
840        content
841    }
842}
843
844#[inline]
845pub fn indent_aware_write(
846    v: &str,
847    rc: &mut RenderContext<'_, '_>,
848    out: &mut dyn Output,
849) -> Result<(), RenderError> {
850    if v.is_empty() {
851        return Ok(());
852    }
853    rc.set_content_produced(true);
854
855    if !v.starts_with(newline_matcher) && rc.get_indent_before_write() {
856        if let Some(indent) = rc.get_indent_string() {
857            out.write(indent)?;
858        }
859    }
860
861    if let Some(indent) = rc.get_indent_string() {
862        support::str::write_indented(v, indent, out)?;
863    } else {
864        out.write(v.as_ref())?;
865    }
866
867    let trailing_newline = v.ends_with(newline_matcher);
868    rc.set_trailing_newline(trailing_newline);
869    rc.set_indent_before_write(trailing_newline);
870
871    Ok(())
872}
873
874impl Renderable for TemplateElement {
875    fn render<'reg: 'rc, 'rc>(
876        &'rc self,
877        registry: &'reg Registry<'reg>,
878        ctx: &'rc Context,
879        rc: &mut RenderContext<'reg, 'rc>,
880        out: &mut dyn Output,
881    ) -> Result<(), RenderError> {
882        match self {
883            RawString(ref v) => indent_aware_write(v.as_ref(), rc, out),
884            Expression(ref ht) | HtmlExpression(ref ht) => {
885                let is_html_expression = matches!(self, HtmlExpression(_));
886                if is_html_expression {
887                    rc.set_disable_escape(true);
888                }
889
890                // test if the expression is to render some value
891                let result = if ht.is_name_only() {
892                    let helper_name = ht.name.expand_as_name(registry, ctx, rc)?;
893                    if helper_exists(&helper_name, registry, rc) {
894                        render_helper(ht, registry, ctx, rc, out)
895                    } else {
896                        debug!("Rendering value: {:?}", ht.name);
897                        let context_json = ht.name.expand(registry, ctx, rc)?;
898                        if context_json.is_value_missing() {
899                            if registry.strict_mode() {
900                                Err(RenderError::strict_error(context_json.relative_path()))
901                            } else {
902                                // helper missing
903                                if let Some(hook) = registry.get_or_load_helper(HELPER_MISSING)? {
904                                    let h = Helper::try_from_template(ht, registry, ctx, rc)?;
905                                    hook.call(&h, registry, ctx, rc, out)
906                                } else {
907                                    Ok(())
908                                }
909                            }
910                        } else {
911                            let rendered = context_json.value().render();
912                            let output = do_escape(registry, rc, rendered);
913                            indent_aware_write(output.as_ref(), rc, out)
914                        }
915                    }
916                } else {
917                    // this is a helper expression
918                    render_helper(ht, registry, ctx, rc, out)
919                };
920
921                if is_html_expression {
922                    rc.set_disable_escape(false);
923                }
924
925                result
926            }
927            HelperBlock(ref ht) => render_helper(ht, registry, ctx, rc, out),
928            DecoratorExpression(_) | DecoratorBlock(_) => self.eval(registry, ctx, rc),
929            PartialExpression(ref dt) | PartialBlock(ref dt) => {
930                let di = Decorator::try_from_template(dt, registry, ctx, rc)?;
931
932                let indent_directive_before = rc.get_indent_before_write();
933                let content_produced_before = rc.get_content_produced();
934
935                rc.set_indent_before_write(
936                    dt.indent_before_write && (rc.get_trailine_newline() || dt.indent.is_some()),
937                );
938                rc.set_content_produced(false);
939
940                partial::expand_partial(&di, registry, ctx, rc, out)?;
941
942                if rc.get_content_produced() {
943                    rc.set_indent_before_write(rc.get_trailine_newline());
944                } else {
945                    rc.set_content_produced(content_produced_before);
946                    rc.set_indent_before_write(indent_directive_before);
947                }
948                Ok(())
949            }
950            _ => Ok(()),
951        }
952    }
953}
954
955impl Evaluable for TemplateElement {
956    fn eval<'reg: 'rc, 'rc>(
957        &'rc self,
958        registry: &'reg Registry<'reg>,
959        ctx: &'rc Context,
960        rc: &mut RenderContext<'reg, 'rc>,
961    ) -> Result<(), RenderError> {
962        match *self {
963            DecoratorExpression(ref dt) | DecoratorBlock(ref dt) => {
964                let di = Decorator::try_from_template(dt, registry, ctx, rc)?;
965                match registry.get_decorator(di.name()) {
966                    Some(d) => d.call(&di, registry, ctx, rc),
967                    None => Err(RenderErrorReason::DecoratorNotFound(di.name().to_owned()).into()),
968                }
969            }
970            _ => Ok(()),
971        }
972    }
973}
974
975#[cfg(test)]
976mod test {
977    use std::collections::BTreeMap;
978
979    use super::{Helper, RenderContext, Renderable};
980    use crate::block::BlockContext;
981    use crate::context::Context;
982    use crate::error::RenderError;
983    use crate::json::path::Path;
984    use crate::json::value::JsonRender;
985    use crate::output::{Output, StringOutput};
986    use crate::registry::Registry;
987    use crate::template::TemplateElement::*;
988    use crate::template::{HelperTemplate, Template, TemplateElement};
989
990    #[test]
991    fn test_raw_string() {
992        let r = Registry::new();
993        let raw_string = RawString("<h1>hello world</h1>".to_string());
994
995        let mut out = StringOutput::new();
996        let ctx = Context::null();
997        {
998            let mut rc = RenderContext::new(None);
999            raw_string.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
1000        }
1001        assert_eq!(
1002            out.into_string().unwrap(),
1003            "<h1>hello world</h1>".to_string()
1004        );
1005    }
1006
1007    #[test]
1008    fn test_expression() {
1009        let r = Registry::new();
1010        let element = Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
1011            &["hello"],
1012        ))));
1013
1014        let mut out = StringOutput::new();
1015        let mut m: BTreeMap<String, String> = BTreeMap::new();
1016        let value = "<p></p>".to_string();
1017        m.insert("hello".to_string(), value);
1018        let ctx = Context::wraps(&m).unwrap();
1019        {
1020            let mut rc = RenderContext::new(None);
1021            element.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
1022        }
1023
1024        assert_eq!(
1025            out.into_string().unwrap(),
1026            "&lt;p&gt;&lt;/p&gt;".to_string()
1027        );
1028    }
1029
1030    #[test]
1031    fn test_html_expression() {
1032        let r = Registry::new();
1033        let element = HtmlExpression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
1034            &["hello"],
1035        ))));
1036
1037        let mut out = StringOutput::new();
1038        let mut m: BTreeMap<String, String> = BTreeMap::new();
1039        let value = "world";
1040        m.insert("hello".to_string(), value.to_string());
1041        let ctx = Context::wraps(&m).unwrap();
1042        {
1043            let mut rc = RenderContext::new(None);
1044            element.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
1045        }
1046
1047        assert_eq!(out.into_string().unwrap(), value.to_string());
1048    }
1049
1050    #[test]
1051    fn test_template() {
1052        let r = Registry::new();
1053        let mut out = StringOutput::new();
1054        let mut m: BTreeMap<String, String> = BTreeMap::new();
1055        let value = "world".to_string();
1056        m.insert("hello".to_string(), value);
1057        let ctx = Context::wraps(&m).unwrap();
1058
1059        let elements: Vec<TemplateElement> = vec![
1060            RawString("<h1>".to_string()),
1061            Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
1062                &["hello"],
1063            )))),
1064            RawString("</h1>".to_string()),
1065            Comment(String::new()),
1066        ];
1067
1068        let template = Template {
1069            elements,
1070            name: None,
1071            mapping: Vec::new(),
1072        };
1073
1074        {
1075            let mut rc = RenderContext::new(None);
1076            template.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
1077        }
1078
1079        assert_eq!(out.into_string().unwrap(), "<h1>world</h1>".to_string());
1080    }
1081
1082    #[test]
1083    fn test_render_context_promotion_and_demotion() {
1084        use crate::json::value::to_json;
1085        let mut render_context = RenderContext::new(None);
1086        let mut block = BlockContext::new();
1087
1088        block.set_local_var("index", to_json(0));
1089        render_context.push_block(block);
1090
1091        render_context.push_block(BlockContext::new());
1092        assert_eq!(
1093            render_context.get_local_var(1, "index").unwrap(),
1094            &to_json(0)
1095        );
1096
1097        render_context.pop_block();
1098
1099        assert_eq!(
1100            render_context.get_local_var(0, "index").unwrap(),
1101            &to_json(0)
1102        );
1103    }
1104
1105    #[test]
1106    fn test_render_subexpression_issue_115() {
1107        use crate::support::str::StringWriter;
1108
1109        let mut r = Registry::new();
1110        r.register_helper(
1111            "format",
1112            Box::new(
1113                |h: &Helper<'_>,
1114                 _: &Registry<'_>,
1115                 _: &Context,
1116                 _: &mut RenderContext<'_, '_>,
1117                 out: &mut dyn Output|
1118                 -> Result<(), RenderError> {
1119                    out.write(&h.param(0).unwrap().value().render())
1120                        .map_err(RenderError::from)
1121                },
1122            ),
1123        );
1124
1125        let mut sw = StringWriter::new();
1126        let mut m: BTreeMap<String, String> = BTreeMap::new();
1127        m.insert("a".to_string(), "123".to_string());
1128
1129        {
1130            if let Err(e) = r.render_template_to_write("{{format (format a)}}", &m, &mut sw) {
1131                panic!("{}", e);
1132            }
1133        }
1134
1135        assert_eq!(sw.into_string(), "123".to_string());
1136    }
1137
1138    #[test]
1139    fn test_render_error_line_no() {
1140        let mut r = Registry::new();
1141        let m: BTreeMap<String, String> = BTreeMap::new();
1142
1143        let name = "invalid_template";
1144        assert!(r
1145            .register_template_string(name, "<h1>\n{{#if true}}\n  {{#each}}{{/each}}\n{{/if}}")
1146            .is_ok());
1147
1148        if let Err(e) = r.render(name, &m) {
1149            assert_eq!(e.line_no.unwrap(), 3);
1150            assert_eq!(e.column_no.unwrap(), 3);
1151            assert_eq!(e.template_name, Some(name.to_owned()));
1152        } else {
1153            panic!("Error expected");
1154        }
1155    }
1156
1157    #[test]
1158    fn test_partial_failback_render() {
1159        let mut r = Registry::new();
1160
1161        assert!(r
1162            .register_template_string("parent", "<html>{{> layout}}</html>")
1163            .is_ok());
1164        assert!(r
1165            .register_template_string(
1166                "child",
1167                "{{#*inline \"layout\"}}content{{/inline}}{{#> parent}}{{> seg}}{{/parent}}",
1168            )
1169            .is_ok());
1170        assert!(r.register_template_string("seg", "1234").is_ok());
1171
1172        let r = r.render("child", &true).expect("should work");
1173        assert_eq!(r, "<html>content</html>");
1174    }
1175
1176    #[test]
1177    fn test_key_with_slash() {
1178        let mut r = Registry::new();
1179
1180        assert!(r
1181            .register_template_string("t", "{{#each this}}{{@key}}: {{this}}\n{{/each}}")
1182            .is_ok());
1183
1184        let r = r.render("t", &json!({"/foo": "bar"})).unwrap();
1185
1186        assert_eq!(r, "/foo: bar\n");
1187    }
1188
1189    #[test]
1190    fn test_comment() {
1191        let r = Registry::new();
1192
1193        assert_eq!(
1194            r.render_template("Hello {{this}} {{! test me }}", &0)
1195                .unwrap(),
1196            "Hello 0 "
1197        );
1198    }
1199
1200    #[test]
1201    fn test_zero_args_heler() {
1202        let mut r = Registry::new();
1203
1204        r.register_helper(
1205            "name",
1206            Box::new(
1207                |_: &Helper<'_>,
1208                 _: &Registry<'_>,
1209                 _: &Context,
1210                 _: &mut RenderContext<'_, '_>,
1211                 out: &mut dyn Output|
1212                 -> Result<(), RenderError> {
1213                    out.write("N/A").map_err(Into::into)
1214                },
1215            ),
1216        );
1217
1218        r.register_template_string("t0", "Output name: {{name}}")
1219            .unwrap();
1220        r.register_template_string("t1", "Output name: {{first_name}}")
1221            .unwrap();
1222        r.register_template_string("t2", "Output name: {{./name}}")
1223            .unwrap();
1224
1225        // when "name" is available in context, use context first
1226        assert_eq!(
1227            r.render("t0", &json!({"name": "Alex"})).unwrap(),
1228            "Output name: N/A"
1229        );
1230
1231        // when "name" is unavailable, call helper with same name
1232        assert_eq!(
1233            r.render("t2", &json!({"name": "Alex"})).unwrap(),
1234            "Output name: Alex"
1235        );
1236
1237        // output nothing when neither context nor helper available
1238        assert_eq!(
1239            r.render("t1", &json!({"name": "Alex"})).unwrap(),
1240            "Output name: "
1241        );
1242
1243        // generate error in strict mode for above case
1244        r.set_strict_mode(true);
1245        assert!(r.render("t1", &json!({"name": "Alex"})).is_err());
1246
1247        // output nothing when helperMissing was defined
1248        r.set_strict_mode(false);
1249        r.register_helper(
1250            "helperMissing",
1251            Box::new(
1252                |h: &Helper<'_>,
1253                 _: &Registry<'_>,
1254                 _: &Context,
1255                 _: &mut RenderContext<'_, '_>,
1256                 out: &mut dyn Output|
1257                 -> Result<(), RenderError> {
1258                    let name = h.name();
1259                    write!(out, "{name} not resolved")?;
1260                    Ok(())
1261                },
1262            ),
1263        );
1264        assert_eq!(
1265            r.render("t1", &json!({"name": "Alex"})).unwrap(),
1266            "Output name: first_name not resolved"
1267        );
1268    }
1269
1270    #[test]
1271    fn test_identifiers_starting_with_numbers() {
1272        let mut r = Registry::new();
1273
1274        assert!(r
1275            .register_template_string("r1", "{{#if 0a}}true{{/if}}")
1276            .is_ok());
1277        let r1 = r.render("r1", &json!({"0a": true})).unwrap();
1278        assert_eq!(r1, "true");
1279
1280        assert!(r.register_template_string("r2", "{{eq 1a 1}}").is_ok());
1281        let r2 = r.render("r2", &json!({"1a": 2, "a": 1})).unwrap();
1282        assert_eq!(r2, "false");
1283
1284        assert!(r
1285            .register_template_string("r3", "0: {{0}} {{#if (eq 0 true)}}resolved from context{{/if}}\n1a: {{1a}} {{#if (eq 1a true)}}resolved from context{{/if}}\n2_2: {{2_2}} {{#if (eq 2_2 true)}}resolved from context{{/if}}") // YUP it is just eq that barfs! is if handled specially? maybe this test should go nearer to specific helpers that fail?
1286            .is_ok());
1287        let r3 = r
1288            .render("r3", &json!({"0": true, "1a": true, "2_2": true}))
1289            .unwrap();
1290        assert_eq!(
1291            r3,
1292            "0: true \n1a: true resolved from context\n2_2: true resolved from context"
1293        );
1294
1295        // these should all be errors:
1296        assert!(r.register_template_string("r4", "{{eq 1}}").is_ok());
1297        assert!(r.register_template_string("r5", "{{eq a1}}").is_ok());
1298        assert!(r.register_template_string("r6", "{{eq 1a}}").is_ok());
1299        assert!(r.render("r4", &()).is_err());
1300        assert!(r.render("r5", &()).is_err());
1301        assert!(r.render("r6", &()).is_err());
1302    }
1303}