From 4d7fcf585afb266566cc4c92f4cd30e67a489a8d Mon Sep 17 00:00:00 2001 From: RumovZ Date: Wed, 28 Jul 2021 11:53:31 +0200 Subject: [PATCH] Check for invalid conditionals on templates --- ftl/core/card-template-rendering.ftl | 3 ++ rslib/src/error/mod.rs | 1 + rslib/src/template.rs | 45 +++++++++++++++++++++++----- 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/ftl/core/card-template-rendering.ftl b/ftl/core/card-template-rendering.ftl index bff3327a0..7141c35f1 100644 --- a/ftl/core/card-template-rendering.ftl +++ b/ftl/core/card-template-rendering.ftl @@ -21,6 +21,9 @@ card-template-rendering-conditional-not-open = Found '{ $found }', but missing ' # when the user referenced a field that doesn't exist # eg, Found '{{Field}}', but there is not field called 'Field' card-template-rendering-no-such-field = Found '{ $found }', but there is no field called '{ $field }' +# when the user referenced a field that doesn't exist in a conditional, excepting card numbers like 'c1' +# eg, Found '{{#Field}}', but neither is it a card number nor is there a field called 'Field' +card-template-rendering-no-such-conditional = Found '{ $found }', but neither is it a card number nor is there a field called '{ $key }' # This message is shown when the front side of the card is blank, # either due to a badly-designed template, or because required fields # are missing. diff --git a/rslib/src/error/mod.rs b/rslib/src/error/mod.rs index 324976a16..907bf2492 100644 --- a/rslib/src/error/mod.rs +++ b/rslib/src/error/mod.rs @@ -119,6 +119,7 @@ pub enum TemplateError { filters: String, field: String, }, + NoSuchConditional(String), } impl From for AnkiError { diff --git a/rslib/src/template.rs b/rslib/src/template.rs index 1bde79f3d..508e4b9f1 100644 --- a/rslib/src/template.rs +++ b/rslib/src/template.rs @@ -297,6 +297,12 @@ fn localized_template_error(tr: &I18n, err: TemplateError) -> String { TemplateError::FieldNotFound { field, filters } => tr .card_template_rendering_no_such_field(format!("{{{{{}{}}}}}", filters, field), field) .into(), + TemplateError::NoSuchConditional(condition) => tr + .card_template_rendering_no_such_conditional( + format!("{{{{{}}}}}", condition), + &condition[1..], + ) + .into(), } } @@ -467,12 +473,12 @@ fn render_into( } } Conditional { key, children } => { - if context.nonempty_fields.contains(key.as_str()) { + if context.evaluate_conditional(key.as_str(), false)? { render_into(rendered_nodes, children.as_ref(), context, tr)?; } } NegatedConditional { key, children } => { - if !context.nonempty_fields.contains(key.as_str()) { + if context.evaluate_conditional(key.as_str(), true)? { render_into(rendered_nodes, children.as_ref(), context, tr)?; } } @@ -482,6 +488,24 @@ fn render_into( Ok(()) } +impl<'a> RenderContext<'a> { + fn evaluate_conditional(&self, key: &str, negated: bool) -> TemplateResult { + if self.nonempty_fields.contains(key) { + Ok(true ^ negated) + } else if self.fields.contains_key(key) + || (key.starts_with('c') && key[1..].parse::().is_ok()) + { + Ok(false ^ negated) + } else { + let prefix = if negated { "^" } else { "#" }; + Err(TemplateError::NoSuchConditional(format!( + "{}{}", + prefix, key + ))) + } + } +} + /// Append to last node if last node is a string, else add new node. fn append_str_to_nodes(nodes: &mut Vec, text: &str) { if let Some(RenderedNode::Text { @@ -1015,7 +1039,7 @@ mod test { #[test] fn render_single() { - let map: HashMap<_, _> = vec![("F", "f"), ("B", "b"), ("E", " ")] + let map: HashMap<_, _> = vec![("F", "f"), ("B", "b"), ("E", " "), ("c1", "1")] .into_iter() .map(|r| (r.0, r.1.into())) .collect(); @@ -1044,10 +1068,8 @@ mod test { // missing tmpl = PT::from_text("{{^M}}A{{/M}}").unwrap(); assert_eq!( - tmpl.render(&ctx, &tr).unwrap(), - vec![FN::Text { - text: "A".to_owned() - },] + tmpl.render(&ctx, &tr).unwrap_err(), + TemplateError::NoSuchConditional("^M".to_string()) ); // nested @@ -1059,6 +1081,15 @@ mod test { },] ); + // card conditionals + tmpl = PT::from_text("{{^c2}}1{{#c1}}2{{/c1}}{{/c2}}").unwrap(); + assert_eq!( + tmpl.render(&ctx, &tr).unwrap(), + vec![FN::Text { + text: "12".to_owned() + },] + ); + // unknown filters tmpl = PT::from_text("{{one:two:B}}").unwrap(); assert_eq!(