From cc09ca34d4c85b9011262a9d5450699ea7ee5435 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sat, 11 Jan 2020 12:07:06 +1000 Subject: [PATCH] cloze: support MathJax still to do --- rslib/src/template_filters.rs | 94 ++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/rslib/src/template_filters.rs b/rslib/src/template_filters.rs index 586766a25..7fe6bf9c9 100644 --- a/rslib/src/template_filters.rs +++ b/rslib/src/template_filters.rs @@ -68,6 +68,8 @@ fn apply_filter<'a>(filter_name: &str, text: &'a str, field_name: &str) -> (bool match base { "type" => type_filter(text, filter_args, field_name), "hint" => hint_filter(text, field_name), + //"cq" => cloze_filter(text, filter_args, true), + //"ca" => cloze_filter(text, filter_args, false), _ => return (false, None), } } @@ -85,6 +87,79 @@ fn apply_filter<'a>(filter_name: &str, text: &'a str, field_name: &str) -> (bool // Cloze filter //---------------------------------------- +lazy_static! { + static ref CLOZE: Regex = Regex::new( + r#"(?xsi) + \{\{ + (c)(\d+):: # 1 = c or C, 2 = cloze number + (.*?) # 3 = clozed text + (?: + ::(.*?) # 4 = optional hint + )? + \}\} + "# + ) + .unwrap(); +} + +mod cloze_caps { + // the lower or uppercase C in the cloze deletion + pub static C_CHAR: usize = 1; + // cloze ordinal + pub static ORD: usize = 2; + // the occluded text + pub static TEXT: usize = 3; + // optional hint + pub static HINT: usize = 4; +} + +fn reveal_cloze_text(text: &str, ord: u16, question: bool) -> Cow { + let output = CLOZE.replace_all(text, |caps: &Captures| { + let captured_ord = caps + .get(cloze_caps::ORD) + .unwrap() + .as_str() + .parse() + .unwrap_or(0); + + if captured_ord != ord { + // other cloze deletions are unchanged + return caps.get(cloze_caps::TEXT).unwrap().as_str().to_owned(); + } + + let mut replacement; + if question { + // hint provided? + if let Some(hint) = caps.get(cloze_caps::HINT) { + replacement = format!("[{}]", hint.as_str()); + } else { + replacement = "[...]".to_string() + } + } else { + replacement = caps.get(cloze_caps::TEXT).unwrap().as_str().to_owned(); + } + + let can_use_html = caps.get(cloze_caps::C_CHAR).unwrap().as_str() == "c"; + if can_use_html { + replacement = format!("{}", replacement); + } + + replacement + }); + + // if no cloze deletions are found, Anki returns an empty string + match output { + Cow::Borrowed(_) => "".into(), + other => other, + } +} + +#[allow(dead_code)] +fn cloze_filter<'a>(text: &'a str, filter_args: &str, question: bool) -> Cow<'a, str> { + let cloze_ord = filter_args.parse().unwrap_or(0); + reveal_cloze_text(text, cloze_ord, question) +} + // Ruby filters //---------------------------------------- @@ -185,7 +260,8 @@ return false;"> #[cfg(test)] mod test { use crate::template_filters::{ - apply_filters, furigana_filter, hint_filter, kana_filter, kanji_filter, type_filter, + apply_filters, cloze_filter, furigana_filter, hint_filter, kana_filter, kanji_filter, + type_filter, }; #[test] @@ -226,4 +302,20 @@ foo ("[[type:cloze:Text]]".into(), vec![]) ); } + + #[test] + fn test_cloze() { + let text = "{{C1::one}} {{C2::two::hint}}"; + assert_eq!(cloze_filter(text, "1", true), "[...] two"); + assert_eq!(cloze_filter(text, "2", true), "one [hint]"); + assert_eq!(cloze_filter(text, "1", false), "one two"); + assert_eq!( + cloze_filter(&text.replace('C', "c"), "1", false), + "one two" + ); + assert_eq!( + cloze_filter(&text.replace('C', "c"), "1", true), + "[...] two" + ); + } }