From 47d4c391453bdeb407bac69c19441e2bf0d6a876 Mon Sep 17 00:00:00 2001 From: Athitheya Gobinathan Date: Sat, 14 Mar 2026 11:07:30 -0700 Subject: [PATCH] FIX(rslib/cloze): Ignore `}}` inside MathJax when tokenizing clozes * FIX premature cloze termination inside MathJax * ADD regression test for MathJax cloze parsing --- CONTRIBUTORS | 1 + rslib/src/cloze.rs | 53 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index a3b1b1c6f84..6c0658d68ec 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -266,6 +266,7 @@ Yuuki Gabriele Patriarca SecretX Daniel Pechersky fernandolins <1887601+fernandolins@users.noreply.github.com> +Athitheya Gobinathan ******************** diff --git a/rslib/src/cloze.rs b/rslib/src/cloze.rs index 70a5d1703a8..ab6f2dfc99c 100644 --- a/rslib/src/cloze.rs +++ b/rslib/src/cloze.rs @@ -91,16 +91,44 @@ fn tokenize(mut text: &str) -> impl Iterator> { nom::error::ErrorKind::Eof, ))); } - let mut other_token = alt((open_cloze, close_cloze)); - // start with the no-match case let mut index = text.len(); - for (idx, _) in text.char_indices() { - if other_token.parse(&text[idx..]).is_ok() { - index = idx; + let mut i = 0; + let mut mathjax_end: Option<&str> = None; + while i < text.len() { + let rest = &text[i..]; + // Cloze openings should be recognized everywhere, including inside MathJax. + if open_cloze(rest).is_ok() { + index = i; break; } + // Cloze closings should only be recognized outside MathJax. + if mathjax_end.is_none() && close_cloze(rest).is_ok() { + index = i; + break; + } + // Enter MathJax when not already inside it. + if mathjax_end.is_none() { + if rest.starts_with(r"\(") { + mathjax_end = Some(r"\)"); + i += 2; + continue; + } + if rest.starts_with(r"\[") { + mathjax_end = Some(r"\]"); + i += 2; + continue; + } + } else if let Some(end) = mathjax_end { + // Exit MathJax when we hit the matching delimiter. + if rest.starts_with(end) { + mathjax_end = None; + i += 2; + continue; + } + } + i += rest.chars().next().unwrap().len_utf8(); } - Ok((&text[index..], Token::Text(&text[0..index]))) + Ok((&text[index..], Token::Text(&text[..index]))) } std::iter::from_fn(move || { @@ -665,6 +693,19 @@ mod test { ); } + #[test] + fn cloze_with_mathjax_braces() { + let text = r"{{c1:: \( \frac{1}{\sqrt{\pi}} \) }}"; + assert_eq!( + strip_html(reveal_cloze_text(text, 1, true).as_ref()), + "[...]" + ); + assert_eq!( + strip_html(reveal_cloze_text(text, 1, false).as_ref()), + r" \( \frac{1}{\sqrt{\pi}} \) " + ); + } + #[test] fn non_latin() { assert!(cloze_numbers_in_string("öaöaöööaö").is_empty());