use std::borrow::{Borrow, Cow};
use std::collections::{BTreeMap, VecDeque};
use std::fmt;
-use std::ops::Deref;
use std::rc::Rc;
use serde_json::value::Value as Json;
#[derive(Clone)]
pub struct RenderContextInner<'reg: 'rc, 'rc> {
partials: BTreeMap<String, &'reg Template>,
- local_helpers: BTreeMap<String, Rc<dyn HelperDef + 'rc>>,
+ partial_block_stack: VecDeque<&'reg Template>,
+ partial_block_depth: isize,
+ local_helpers: BTreeMap<String, Rc<dyn HelperDef + Send + Sync + 'rc>>,
/// current template name
current_template: Option<&'reg String>,
/// root template name
pub fn new(root_template: Option<&'reg String>) -> RenderContext<'reg, 'rc> {
let inner = Rc::new(RenderContextInner {
partials: BTreeMap::new(),
+ partial_block_stack: VecDeque::new(),
+ partial_block_depth: 0,
local_helpers: BTreeMap::new(),
current_template: None,
root_template,
}
/// Get registered partial in this render context
- pub fn get_partial(&self, name: &str) -> Option<&&Template> {
- self.inner().partials.get(name)
+ pub fn get_partial(&self, name: &str) -> Option<&Template> {
+ if name == partial::PARTIAL_BLOCK {
+ return self
+ .inner()
+ .partial_block_stack
+ .get(self.inner().partial_block_depth as usize)
+ .copied();
+ }
+ self.inner().partials.get(name).copied()
}
/// Register a partial for this context
self.inner_mut().partials.insert(name, partial);
}
+ pub(crate) fn push_partial_block(&mut self, partial: &'reg Template) {
+ self.inner_mut().partial_block_stack.push_front(partial);
+ }
+
+ pub(crate) fn pop_partial_block(&mut self) {
+ self.inner_mut().partial_block_stack.pop_front();
+ }
+
+ pub(crate) fn inc_partial_block_depth(&mut self) {
+ self.inner_mut().partial_block_depth += 1;
+ }
+
+ pub(crate) fn dec_partial_block_depth(&mut self) {
+ self.inner_mut().partial_block_depth -= 1;
+ }
+
/// Remove a registered partial
pub fn remove_partial(&mut self, name: &str) {
self.inner_mut().partials.remove(name);
pub fn register_local_helper(
&mut self,
name: &str,
- def: Box<dyn HelperDef + 'rc>,
- ) -> Option<Rc<dyn HelperDef + 'rc>> {
+ def: Box<dyn HelperDef + Send + Sync + 'rc>,
+ ) {
self.inner_mut()
.local_helpers
- .insert(name.to_string(), def.into())
+ .insert(name.to_string(), def.into());
}
/// Remove a helper from render context
}
/// Attempt to get a helper from current render context.
- pub fn get_local_helper(&self, name: &str) -> Option<Rc<dyn HelperDef + 'rc>> {
+ pub fn get_local_helper(&self, name: &str) -> Option<Rc<dyn HelperDef + Send + Sync + 'rc>> {
self.inner().local_helpers.get(name).cloned()
}
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.debug_struct("RenderContextInner")
.field("partials", &self.partials)
+ .field("partial_block_stack", &self.partial_block_stack)
.field("root_template", &self.root_template)
.field("current_template", &self.current_template)
.field("disable_eacape", &self.disable_escape)
) -> Result<(), RenderError>;
}
+#[inline]
fn call_helper_for_value<'reg: 'rc, 'rc>(
hd: &dyn HelperDef,
ht: &Helper<'reg, 'rc>,
ctx: &'rc Context,
rc: &mut RenderContext<'reg, 'rc>,
) -> Result<PathAndJson<'reg, 'rc>, RenderError> {
- if let Some(result) = hd.call_inner(ht, r, ctx, rc)? {
- Ok(PathAndJson::new(None, result))
- } else {
- // parse value from output
- let mut so = StringOutput::new();
-
- // here we don't want subexpression result escaped,
- // so we temporarily disable it
- let disable_escape = rc.is_disable_escape();
- rc.set_disable_escape(true);
-
- hd.call(ht, r, ctx, rc, &mut so)?;
- rc.set_disable_escape(disable_escape);
-
- let string = so.into_string().map_err(RenderError::from)?;
- Ok(PathAndJson::new(
- None,
- ScopedJson::Derived(Json::String(string)),
- ))
+ match hd.call_inner(ht, r, ctx, rc) {
+ Ok(result) => Ok(PathAndJson::new(None, result)),
+ Err(e) => {
+ if e.is_unimplemented() {
+ // parse value from output
+ let mut so = StringOutput::new();
+
+ // here we don't want subexpression result escaped,
+ // so we temporarily disable it
+ let disable_escape = rc.is_disable_escape();
+ rc.set_disable_escape(true);
+
+ hd.call(ht, r, ctx, rc, &mut so)?;
+ rc.set_disable_escape(disable_escape);
+
+ let string = so.into_string().map_err(RenderError::from)?;
+ Ok(PathAndJson::new(
+ None,
+ ScopedJson::Derived(Json::String(string)),
+ ))
+ } else {
+ Err(e)
+ }
+ }
}
}
Parameter::Literal(ref j) => Ok(PathAndJson::new(None, ScopedJson::Constant(j))),
Parameter::Subexpression(ref t) => match *t.as_element() {
Expression(ref ht) => {
- if ht.is_name_only() {
- ht.name.expand(registry, ctx, rc)
+ let name = ht.name.expand_as_name(registry, ctx, rc)?;
+
+ let h = Helper::try_from_template(ht, registry, ctx, rc)?;
+ if let Some(ref d) = rc.get_local_helper(&name) {
+ call_helper_for_value(d.as_ref(), &h, registry, ctx, rc)
} else {
- let name = ht.name.expand_as_name(registry, ctx, rc)?;
+ let mut helper = registry.get_or_load_helper(&name)?;
- let h = Helper::try_from_template(ht, registry, ctx, rc)?;
- if let Some(ref d) = rc.get_local_helper(&name) {
- let helper_def = d.deref();
- call_helper_for_value(helper_def, &h, registry, ctx, rc)
- } else {
- registry
- .get_helper(&name)
- .or_else(|| {
- registry.get_helper(if ht.block {
- BLOCK_HELPER_MISSING
- } else {
- HELPER_MISSING
- })
- })
- .ok_or_else(|| {
- RenderError::new(format!("Helper not defined: {:?}", ht.name))
- })
- .and_then(move |d| call_helper_for_value(d, &h, registry, ctx, rc))
+ if helper.is_none() {
+ helper = registry.get_or_load_helper(if ht.block {
+ BLOCK_HELPER_MISSING
+ } else {
+ HELPER_MISSING
+ })?;
}
+
+ helper
+ .ok_or_else(|| {
+ RenderError::new(format!("Helper not defined: {:?}", ht.name))
+ })
+ .and_then(|d| call_helper_for_value(d.as_ref(), &h, registry, ctx, rc))
}
}
_ => unreachable!(),
t.render(registry, ctx, rc, out).map_err(|mut e| {
// add line/col number if the template has mapping data
if e.line_no.is_none() {
- if let Some(ref mapping) = self.mapping {
- if let Some(&TemplateMapping(line, col)) = mapping.get(idx) {
- e.line_no = Some(line);
- e.column_no = Some(col);
- }
+ if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) {
+ e.line_no = Some(line);
+ e.column_no = Some(col);
}
}
for (idx, t) in iter.enumerate() {
t.eval(registry, ctx, rc).map_err(|mut e| {
if e.line_no.is_none() {
- if let Some(ref mapping) = self.mapping {
- if let Some(&TemplateMapping(line, col)) = mapping.get(idx) {
- e.line_no = Some(line);
- e.column_no = Some(col);
- }
+ if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) {
+ e.line_no = Some(line);
+ e.column_no = Some(col);
}
}
rc.has_local_helper(name) || reg.has_helper(name)
}
+#[inline]
fn render_helper<'reg: 'rc, 'rc>(
ht: &'reg HelperTemplate,
registry: &'reg Registry<'reg>,
if let Some(ref d) = rc.get_local_helper(h.name()) {
d.call(&h, registry, ctx, rc, out)
} else {
- registry
- .get_helper(h.name())
- .or_else(|| {
- registry.get_helper(if ht.block {
- BLOCK_HELPER_MISSING
- } else {
- HELPER_MISSING
- })
- })
- .ok_or_else(|| RenderError::new(format!("Helper not defined: {:?}", ht.name)))
- .and_then(move |d| d.call(&h, registry, ctx, rc, out))
+ let mut helper = registry.get_or_load_helper(h.name())?;
+
+ if helper.is_none() {
+ helper = registry.get_or_load_helper(if ht.block {
+ BLOCK_HELPER_MISSING
+ } else {
+ HELPER_MISSING
+ })?;
+ }
+
+ helper
+ .ok_or_else(|| RenderError::new(format!("Helper not defined: {:?}", h.name())))
+ .and_then(|d| d.call(&h, registry, ctx, rc, out))
}
}
out.write(v.as_ref())?;
Ok(())
}
- Expression(ref ht) => {
+ Expression(ref ht) | HtmlExpression(ref ht) => {
+ let is_html_expression = matches!(self, HtmlExpression(_));
+ if is_html_expression {
+ rc.set_disable_escape(true);
+ }
+
// test if the expression is to render some value
- if ht.is_name_only() {
+ let result = if ht.is_name_only() {
let helper_name = ht.name.expand_as_name(registry, ctx, rc)?;
if helper_exists(&helper_name, registry, rc) {
render_helper(ht, registry, ctx, rc, out)
Err(RenderError::strict_error(context_json.relative_path()))
} else {
// helper missing
- if let Some(hook) = registry.get_helper(HELPER_MISSING) {
+ if let Some(hook) = registry.get_or_load_helper(HELPER_MISSING)? {
let h = Helper::try_from_template(ht, registry, ctx, rc)?;
hook.call(&h, registry, ctx, rc, out)
} else {
} else {
// this is a helper expression
render_helper(ht, registry, ctx, rc, out)
- }
- }
- HTMLExpression(ref v) => {
- debug!("Rendering value: {:?}", v);
- let context_json = v.expand(registry, ctx, rc)?;
+ };
- // strict mode check
- if registry.strict_mode() && context_json.is_value_missing() {
- return Err(RenderError::strict_error(context_json.relative_path()));
+ if is_html_expression {
+ rc.set_disable_escape(false);
}
- let rendered = context_json.value().render();
- out.write(rendered.as_ref())?;
- Ok(())
+ result
}
HelperBlock(ref ht) => render_helper(ht, registry, ctx, rc, out),
DecoratorExpression(_) | DecoratorBlock(_) => self.eval(registry, ctx, rc),
#[test]
fn test_html_expression() {
let r = Registry::new();
- let element = HTMLExpression(Parameter::Path(Path::with_named_paths(&["hello"])));
+ let element = HtmlExpression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
+ &["hello"],
+ ))));
let mut out = StringOutput::new();
let mut m: BTreeMap<String, String> = BTreeMap::new();
let template = Template {
elements,
name: None,
- mapping: None,
+ mapping: Vec::new(),
};
{
let mut render_context = RenderContext::new(None);
let mut block = BlockContext::new();
- block.set_local_var("@index".to_string(), to_json(0));
+ block.set_local_var("index", to_json(0));
render_context.push_block(block);
render_context.push_block(BlockContext::new());
);
}
-#[test]
-fn test_render_subexpression() {
- use crate::support::str::StringWriter;
-
- let r = Registry::new();
- let mut sw = StringWriter::new();
-
- let mut m: BTreeMap<String, String> = BTreeMap::new();
- m.insert("hello".to_string(), "world".to_string());
- m.insert("world".to_string(), "nice".to_string());
- m.insert("const".to_string(), "truthy".to_string());
-
- {
- if let Err(e) =
- r.render_template_to_write("<h1>{{#if (const)}}{{(hello)}}{{/if}}</h1>", &m, &mut sw)
- {
- panic!("{}", e);
- }
- }
-
- assert_eq!(sw.into_string(), "<h1>world</h1>".to_string());
-}
-
#[test]
fn test_render_subexpression_issue_115() {
use crate::support::str::StringWriter;
r.register_helper(
"helperMissing",
Box::new(
- |_: &Helper<'_, '_>,
+ |h: &Helper<'_, '_>,
_: &Registry<'_>,
_: &Context,
_: &mut RenderContext<'_, '_>,
out: &mut dyn Output|
- -> Result<(), RenderError> { out.write("Default").map_err(Into::into) },
+ -> Result<(), RenderError> {
+ let name = h.name();
+ out.write(&format!("{} not resolved", name))?;
+ Ok(())
+ },
),
);
assert_eq!(
r.render("t1", &json!({"name": "Alex"})).unwrap(),
- "Output name: Default"
+ "Output name: first_name not resolved"
);
}