Check for invalid conditionals on templates

This commit is contained in:
RumovZ 2021-07-28 11:53:31 +02:00
parent 156970c1b4
commit 4d7fcf585a
3 changed files with 42 additions and 7 deletions

View file

@ -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.

View file

@ -119,6 +119,7 @@ pub enum TemplateError {
filters: String,
field: String,
},
NoSuchConditional(String),
}
impl From<io::Error> for AnkiError {

View file

@ -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<bool> {
if self.nonempty_fields.contains(key) {
Ok(true ^ negated)
} else if self.fields.contains_key(key)
|| (key.starts_with('c') && key[1..].parse::<u32>().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<RenderedNode>, 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!(