mirror of
https://github.com/ankitects/anki.git
synced 2025-09-20 15:02:21 -04:00
Expose cloze text as HTML attribute on question side (#1968)
* Expose cloze text as attribute on front side * Update test_models.py * Update template_filters.rs * Escape HTML for data-attribute * Use minimal HTML encoding in Rust to match Python's html.escape and pass tests. * Rename attribute to data-cloze to make it more generic. * Run formatter * Revert to using Rust encode_attribute and add helper function for tests
This commit is contained in:
parent
d482e90c6b
commit
d1ba48bc48
3 changed files with 74 additions and 23 deletions
|
@ -2,6 +2,8 @@
|
|||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
# coding: utf-8
|
||||
import html
|
||||
import re
|
||||
import time
|
||||
|
||||
from anki.consts import MODEL_CLOZE
|
||||
|
@ -10,6 +12,12 @@ from anki.utils import is_win, strip_html
|
|||
from tests.shared import getEmptyCol
|
||||
|
||||
|
||||
def encode_attribute(s):
|
||||
return "".join(
|
||||
c if c.isalnum() else "&#x{:X};".format(ord(c)) for c in html.escape(s)
|
||||
)
|
||||
|
||||
|
||||
def test_modelDelete():
|
||||
col = getEmptyCol()
|
||||
note = col.newNote()
|
||||
|
@ -176,29 +184,41 @@ def test_cloze():
|
|||
note = col.new_note(m)
|
||||
note["Text"] = "hello {{c1::world}}"
|
||||
assert col.addNote(note) == 1
|
||||
assert "hello <span class=cloze>[...]</span>" in note.cards()[0].question()
|
||||
assert "hello <span class=cloze>world</span>" in note.cards()[0].answer()
|
||||
assert (
|
||||
f'hello <span class="cloze" data-cloze="{encode_attribute("world")}">[...]</span>'
|
||||
in note.cards()[0].question()
|
||||
)
|
||||
assert 'hello <span class="cloze">world</span>' in note.cards()[0].answer()
|
||||
# and with a comment
|
||||
note = col.new_note(m)
|
||||
note["Text"] = "hello {{c1::world::typical}}"
|
||||
assert col.addNote(note) == 1
|
||||
assert "<span class=cloze>[typical]</span>" in note.cards()[0].question()
|
||||
assert "<span class=cloze>world</span>" in note.cards()[0].answer()
|
||||
assert (
|
||||
f'<span class="cloze" data-cloze="{encode_attribute("world")}">[typical]</span>'
|
||||
in note.cards()[0].question()
|
||||
)
|
||||
assert '<span class="cloze">world</span>' in note.cards()[0].answer()
|
||||
# and with 2 clozes
|
||||
note = col.new_note(m)
|
||||
note["Text"] = "hello {{c1::world}} {{c2::bar}}"
|
||||
assert col.addNote(note) == 2
|
||||
(c1, c2) = note.cards()
|
||||
assert "<span class=cloze>[...]</span> bar" in c1.question()
|
||||
assert "<span class=cloze>world</span> bar" in c1.answer()
|
||||
assert "world <span class=cloze>[...]</span>" in c2.question()
|
||||
assert "world <span class=cloze>bar</span>" in c2.answer()
|
||||
assert (
|
||||
f'<span class="cloze" data-cloze="{encode_attribute("world")}">[...]</span> bar'
|
||||
in c1.question()
|
||||
)
|
||||
assert '<span class="cloze">world</span> bar' in c1.answer()
|
||||
assert (
|
||||
f'world <span class="cloze" data-cloze="{encode_attribute("bar")}">[...]</span>'
|
||||
in c2.question()
|
||||
)
|
||||
assert 'world <span class="cloze">bar</span>' in c2.answer()
|
||||
# if there are multiple answers for a single cloze, they are given in a
|
||||
# list
|
||||
note = col.new_note(m)
|
||||
note["Text"] = "a {{c1::b}} {{c1::c}}"
|
||||
assert col.addNote(note) == 1
|
||||
assert "<span class=cloze>b</span> <span class=cloze>c</span>" in (
|
||||
assert '<span class="cloze">b</span> <span class="cloze">c</span>' in (
|
||||
note.cards()[0].answer()
|
||||
)
|
||||
# if we add another cloze, a card should be generated
|
||||
|
@ -216,16 +236,42 @@ def test_cloze_mathjax():
|
|||
col = getEmptyCol()
|
||||
m = col.models.by_name("Cloze")
|
||||
note = col.new_note(m)
|
||||
q1 = "ok"
|
||||
q2 = "not ok"
|
||||
q3 = "2"
|
||||
q4 = "blah"
|
||||
q5 = "text with \(x^2\) jax"
|
||||
note[
|
||||
"Text"
|
||||
] = r"{{c1::ok}} \(2^2\) {{c2::not ok}} \(2^{{c3::2}}\) \(x^3\) {{c4::blah}} {{c5::text with \(x^2\) jax}}"
|
||||
] = "{{{{c1::{}}}}} \(2^2\) {{{{c2::{}}}}} \(2^{{{{c3::{}}}}}\) \(x^3\) {{{{c4::{}}}}} {{{{c5::{}}}}}".format(
|
||||
q1,
|
||||
q2,
|
||||
q3,
|
||||
q4,
|
||||
q5,
|
||||
)
|
||||
assert col.addNote(note)
|
||||
assert len(note.cards()) == 5
|
||||
assert "class=cloze" in note.cards()[0].question()
|
||||
assert "class=cloze" in note.cards()[1].question()
|
||||
assert "class=cloze" not in note.cards()[2].question()
|
||||
assert "class=cloze" in note.cards()[3].question()
|
||||
assert "class=cloze" in note.cards()[4].question()
|
||||
assert (
|
||||
f'class="cloze" data-cloze="{encode_attribute(q1)}"'
|
||||
in note.cards()[0].question()
|
||||
)
|
||||
assert (
|
||||
f'class="cloze" data-cloze="{encode_attribute(q2)}"'
|
||||
in note.cards()[1].question()
|
||||
)
|
||||
assert (
|
||||
f'class="cloze" data-cloze="{encode_attribute(q3)}"'
|
||||
not in note.cards()[2].question()
|
||||
)
|
||||
assert (
|
||||
f'class="cloze" data-cloze="{encode_attribute(q4)}"'
|
||||
in note.cards()[3].question()
|
||||
)
|
||||
assert (
|
||||
f'class="cloze" data-cloze="{encode_attribute(q5)}"'
|
||||
in note.cards()[4].question()
|
||||
)
|
||||
|
||||
note = col.new_note(m)
|
||||
note["Text"] = r"\(a\) {{c1::b}} \[ {{c1::c}} \]"
|
||||
|
@ -234,7 +280,7 @@ def test_cloze_mathjax():
|
|||
assert (
|
||||
note.cards()[0]
|
||||
.question()
|
||||
.endswith(r"\(a\) <span class=cloze>[...]</span> \[ [...] \]")
|
||||
.endswith(r'\(a\) <span class="cloze" data-cloze="b">[...]</span> \[ [...] \]')
|
||||
)
|
||||
|
||||
|
||||
|
@ -278,11 +324,12 @@ def test_chained_mods():
|
|||
)
|
||||
assert col.addNote(note) == 1
|
||||
assert (
|
||||
"This <span class=cloze>[sentence]</span> demonstrates <span class=cloze>[chained]</span> clozes."
|
||||
f'This <span class="cloze" data-cloze="{encode_attribute("phrase")}">[sentence]</span>'
|
||||
f' demonstrates <span class="cloze" data-cloze="{encode_attribute("en chaine")}">[chained]</span> clozes.'
|
||||
in note.cards()[0].question()
|
||||
)
|
||||
assert (
|
||||
"This <span class=cloze>phrase</span> demonstrates <span class=cloze>en chaine</span> clozes."
|
||||
f'This <span class="cloze">phrase</span> demonstrates <span class="cloze">en chaine</span> clozes.'
|
||||
in note.cards()[0].answer()
|
||||
)
|
||||
|
||||
|
|
|
@ -57,26 +57,30 @@ pub fn reveal_cloze_text(text: &str, cloze_ord: u16, question: bool) -> Cow<str>
|
|||
.parse()
|
||||
.unwrap_or(0);
|
||||
|
||||
let text = caps.get(cloze_caps::TEXT).unwrap().as_str().to_owned();
|
||||
if captured_ord != cloze_ord {
|
||||
// other cloze deletions are unchanged
|
||||
return caps.get(cloze_caps::TEXT).unwrap().as_str().to_owned();
|
||||
return text;
|
||||
} else {
|
||||
cloze_ord_was_in_text = true;
|
||||
}
|
||||
|
||||
let text_attr;
|
||||
let replacement;
|
||||
if question {
|
||||
text_attr = format!(r#" data-cloze="{}""#, htmlescape::encode_attribute(&text));
|
||||
// hint provided?
|
||||
if let Some(hint) = caps.get(cloze_caps::HINT) {
|
||||
replacement = format!("[{}]", hint.as_str());
|
||||
} else {
|
||||
replacement = "[...]".to_string()
|
||||
replacement = "[...]".to_string();
|
||||
}
|
||||
} else {
|
||||
replacement = caps.get(cloze_caps::TEXT).unwrap().as_str().to_owned();
|
||||
text_attr = "".to_string();
|
||||
replacement = text;
|
||||
}
|
||||
|
||||
format!("<span class=cloze>{}</span>", replacement)
|
||||
format!(r#"<span class="cloze"{}>{}</span>"#, text_attr, replacement)
|
||||
});
|
||||
|
||||
if !cloze_ord_was_in_text {
|
||||
|
|
|
@ -256,7 +256,7 @@ field</a>
|
|||
assert_eq!(strip_html(&cloze_filter(text, &ctx)).as_ref(), "[...] two");
|
||||
assert_eq!(
|
||||
cloze_filter(text, &ctx),
|
||||
"<span class=cloze>[...]</span> two"
|
||||
r#"<span class="cloze" data-cloze="one">[...]</span> two"#
|
||||
);
|
||||
|
||||
ctx.card_ord = 1;
|
||||
|
|
Loading…
Reference in a new issue