mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -04:00
reuse reveal_cloze_text() for LaTeX cloze expansion
This commit is contained in:
parent
9ad80f4d2c
commit
c075191697
7 changed files with 72 additions and 59 deletions
|
@ -18,6 +18,7 @@ message BackendInput {
|
||||||
int64 local_minutes_west = 22;
|
int64 local_minutes_west = 22;
|
||||||
string strip_av_tags = 23;
|
string strip_av_tags = 23;
|
||||||
ExtractAVTagsIn extract_av_tags = 24;
|
ExtractAVTagsIn extract_av_tags = 24;
|
||||||
|
string expand_clozes_to_reveal_latex = 25;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +33,7 @@ message BackendOutput {
|
||||||
sint32 local_minutes_west = 22;
|
sint32 local_minutes_west = 22;
|
||||||
string strip_av_tags = 23;
|
string strip_av_tags = 23;
|
||||||
ExtractAVTagsOut extract_av_tags = 24;
|
ExtractAVTagsOut extract_av_tags = 24;
|
||||||
|
string expand_clozes_to_reveal_latex = 25;
|
||||||
|
|
||||||
BackendError error = 2047;
|
BackendError error = 2047;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
@ -14,11 +17,11 @@ import urllib.request
|
||||||
import zipfile
|
import zipfile
|
||||||
from typing import Any, Callable, List, Optional, Tuple, Union
|
from typing import Any, Callable, List, Optional, Tuple, Union
|
||||||
|
|
||||||
|
import anki
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
from anki.db import DB, DBError
|
from anki.db import DB, DBError
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from anki.latex import render_latex
|
from anki.latex import render_latex
|
||||||
from anki.template import expand_clozes
|
|
||||||
from anki.utils import checksum, isMac, isWin
|
from anki.utils import checksum, isMac, isWin
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,7 +37,7 @@ class MediaManager:
|
||||||
regexps = soundRegexps + imgRegexps
|
regexps = soundRegexps + imgRegexps
|
||||||
db: Optional[DB]
|
db: Optional[DB]
|
||||||
|
|
||||||
def __init__(self, col, server: bool) -> None:
|
def __init__(self, col: anki.storage._Collection, server: bool) -> None:
|
||||||
self.col = col
|
self.col = col
|
||||||
if server:
|
if server:
|
||||||
self._dir = None
|
self._dir = None
|
||||||
|
@ -213,23 +216,21 @@ create table meta (dirMod int, lastUsn int); insert into meta values (0, 0);
|
||||||
) -> List[str]:
|
) -> List[str]:
|
||||||
l = []
|
l = []
|
||||||
model = self.col.models.get(mid)
|
model = self.col.models.get(mid)
|
||||||
strings: List[str] = []
|
|
||||||
if model["type"] == MODEL_CLOZE and "{{c" in string:
|
if model["type"] == MODEL_CLOZE and "{{c" in string:
|
||||||
# if the field has clozes in it, we'll need to expand the
|
# if the field has clozes in it, we'll need to expand the
|
||||||
# possibilities so we can render latex
|
# possibilities so we can render latex
|
||||||
strings = expand_clozes(string)
|
strings = self.col.backend.expand_clozes_to_reveal_latex(string)
|
||||||
else:
|
else:
|
||||||
strings = [string]
|
strings = string
|
||||||
for string in strings:
|
# handle latex
|
||||||
# handle latex
|
string = render_latex(string, model, self.col)
|
||||||
string = render_latex(string, model, self.col)
|
# extract filenames
|
||||||
# extract filenames
|
for reg in self.regexps:
|
||||||
for reg in self.regexps:
|
for match in re.finditer(reg, string):
|
||||||
for match in re.finditer(reg, string):
|
fname = match.group("fname")
|
||||||
fname = match.group("fname")
|
isLocal = not re.match("(https?|ftp)://", fname.lower())
|
||||||
isLocal = not re.match("(https?|ftp)://", fname.lower())
|
if isLocal or includeRemote:
|
||||||
if isLocal or includeRemote:
|
l.append(fname)
|
||||||
l.append(fname)
|
|
||||||
return l
|
return l
|
||||||
|
|
||||||
def transformNames(self, txt: str, func: Callable) -> Any:
|
def transformNames(self, txt: str, func: Callable) -> Any:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
# pylint: skip-file
|
# pylint: skip-file
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Dict, List, Tuple, Union
|
from typing import Dict, List, Tuple, Union
|
||||||
|
|
||||||
|
@ -175,3 +176,8 @@ class RustBackend:
|
||||||
native_tags = list(map(av_tag_to_native, out.av_tags))
|
native_tags = list(map(av_tag_to_native, out.av_tags))
|
||||||
|
|
||||||
return out.text, native_tags
|
return out.text, native_tags
|
||||||
|
|
||||||
|
def expand_clozes_to_reveal_latex(self, text: str) -> str:
|
||||||
|
return self._run_command(
|
||||||
|
pb.BackendInput(expand_clozes_to_reveal_latex=text)
|
||||||
|
).expand_clozes_to_reveal_latex
|
||||||
|
|
|
@ -28,7 +28,6 @@ template_legacy.py file, using the legacy addHook() system.
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
@ -221,44 +220,3 @@ def apply_custom_filters(
|
||||||
|
|
||||||
res += field_text
|
res += field_text
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
# Cloze handling
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
# Matches a {{c123::clozed-out text::hint}} Cloze deletion, case-insensitively.
|
|
||||||
# The regex should be interpolated with a regex number and creates the following
|
|
||||||
# named groups:
|
|
||||||
# - tag: The lowercase or uppercase 'c' letter opening the Cloze.
|
|
||||||
# The c/C difference is only relevant to the legacy code.
|
|
||||||
# - content: Clozed-out content.
|
|
||||||
# - hint: Cloze hint, if provided.
|
|
||||||
clozeReg = r"(?si)\{\{(?P<tag>c)%s::(?P<content>.*?)(::(?P<hint>.*?))?\}\}"
|
|
||||||
|
|
||||||
# Constants referring to group names within clozeReg.
|
|
||||||
CLOZE_REGEX_MATCH_GROUP_TAG = "tag"
|
|
||||||
CLOZE_REGEX_MATCH_GROUP_CONTENT = "content"
|
|
||||||
CLOZE_REGEX_MATCH_GROUP_HINT = "hint"
|
|
||||||
|
|
||||||
# used by the media check functionality
|
|
||||||
def expand_clozes(string: str) -> List[str]:
|
|
||||||
"Render all clozes in string."
|
|
||||||
ords = set(re.findall(r"{{c(\d+)::.+?}}", string))
|
|
||||||
strings = []
|
|
||||||
|
|
||||||
def qrepl(m):
|
|
||||||
if m.group(CLOZE_REGEX_MATCH_GROUP_HINT):
|
|
||||||
return "[%s]" % m.group(CLOZE_REGEX_MATCH_GROUP_HINT)
|
|
||||||
else:
|
|
||||||
return "[...]"
|
|
||||||
|
|
||||||
def arepl(m):
|
|
||||||
return m.group(CLOZE_REGEX_MATCH_GROUP_CONTENT)
|
|
||||||
|
|
||||||
for ord in ords:
|
|
||||||
s = re.sub(clozeReg % ord, qrepl, string)
|
|
||||||
s = re.sub(clozeReg % ".+?", arepl, s)
|
|
||||||
strings.append(s)
|
|
||||||
strings.append(re.sub(clozeReg % ".+?", arepl, string))
|
|
||||||
|
|
||||||
return strings
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
use crate::backend_proto as pt;
|
use crate::backend_proto as pt;
|
||||||
use crate::backend_proto::backend_input::Value;
|
use crate::backend_proto::backend_input::Value;
|
||||||
use crate::backend_proto::RenderedTemplateReplacement;
|
use crate::backend_proto::RenderedTemplateReplacement;
|
||||||
|
use crate::cloze::expand_clozes_to_reveal_latex;
|
||||||
use crate::err::{AnkiError, Result};
|
use crate::err::{AnkiError, Result};
|
||||||
use crate::sched::{local_minutes_west_for_stamp, sched_timing_today};
|
use crate::sched::{local_minutes_west_for_stamp, sched_timing_today};
|
||||||
use crate::template::{
|
use crate::template::{
|
||||||
|
@ -101,6 +102,9 @@ impl Backend {
|
||||||
}
|
}
|
||||||
Value::StripAvTags(text) => OValue::StripAvTags(strip_av_tags(&text).into()),
|
Value::StripAvTags(text) => OValue::StripAvTags(strip_av_tags(&text).into()),
|
||||||
Value::ExtractAvTags(input) => OValue::ExtractAvTags(self.extract_av_tags(input)),
|
Value::ExtractAvTags(input) => OValue::ExtractAvTags(self.extract_av_tags(input)),
|
||||||
|
Value::ExpandClozesToRevealLatex(input) => {
|
||||||
|
OValue::ExpandClozesToRevealLatex(expand_clozes_to_reveal_latex(&input))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
use crate::template::RenderContext;
|
use crate::template::RenderContext;
|
||||||
use crate::text::strip_html;
|
use crate::text::{contains_latex, strip_html};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use regex::Captures;
|
use regex::Captures;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
@ -91,6 +91,23 @@ pub fn reveal_cloze_text(text: &str, cloze_ord: u16, question: bool) -> Cow<str>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If text contains any LaTeX tags, render the front and back
|
||||||
|
/// of each cloze deletion so that LaTeX can be generated. If
|
||||||
|
/// no LaTeX is found, returns an empty string.
|
||||||
|
pub fn expand_clozes_to_reveal_latex(text: &str) -> String {
|
||||||
|
if !contains_latex(text) {
|
||||||
|
return "".into();
|
||||||
|
}
|
||||||
|
let ords = cloze_numbers_in_string(text);
|
||||||
|
let mut buf = String::new();
|
||||||
|
for ord in ords {
|
||||||
|
buf += reveal_cloze_text(text, ord, true).as_ref();
|
||||||
|
buf += reveal_cloze_text(text, ord, false).as_ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cloze_numbers_in_string(html: &str) -> HashSet<u16> {
|
pub fn cloze_numbers_in_string(html: &str) -> HashSet<u16> {
|
||||||
let mut hash = HashSet::with_capacity(4);
|
let mut hash = HashSet::with_capacity(4);
|
||||||
for cap in CLOZE.captures_iter(html) {
|
for cap in CLOZE.captures_iter(html) {
|
||||||
|
@ -122,7 +139,8 @@ pub(crate) fn cloze_filter<'a>(text: &'a str, context: &RenderContext) -> Cow<'a
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::cloze::cloze_numbers_in_string;
|
use crate::cloze::{cloze_numbers_in_string, expand_clozes_to_reveal_latex};
|
||||||
|
use crate::text::strip_html;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -135,5 +153,16 @@ mod test {
|
||||||
cloze_numbers_in_string("{{c2::te}}{{c1::s}}t{{"),
|
cloze_numbers_in_string("{{c2::te}}{{c1::s}}t{{"),
|
||||||
vec![1, 2].into_iter().collect::<HashSet<u16>>()
|
vec![1, 2].into_iter().collect::<HashSet<u16>>()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
expand_clozes_to_reveal_latex("{{c1::foo}} {{c2::bar::baz}}"),
|
||||||
|
"".to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
let expanded = expand_clozes_to_reveal_latex("[latex]{{c1::foo}} {{c2::bar::baz}}[/latex]");
|
||||||
|
let expanded = strip_html(expanded.as_ref());
|
||||||
|
assert!(expanded.contains("foo [baz]"));
|
||||||
|
assert!(expanded.contains("[...] bar"));
|
||||||
|
assert!(expanded.contains("foo bar"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,15 @@ lazy_static! {
|
||||||
(.*?) # 3 - field text
|
(.*?) # 3 - field text
|
||||||
\[/anki:tts\]
|
\[/anki:tts\]
|
||||||
"#).unwrap();
|
"#).unwrap();
|
||||||
|
|
||||||
|
static ref LATEX: Regex = Regex::new(
|
||||||
|
r#"(?xsi)
|
||||||
|
\[latex\](.+?)\[/latex\] # 1 - standard latex
|
||||||
|
|
|
||||||
|
\[\$\](.+?)\[/\$\] # 2 - inline math
|
||||||
|
|
|
||||||
|
\[\$\$\](.+?)\[/\$\$\] # 3 - math environment
|
||||||
|
"#).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn strip_html(html: &str) -> Cow<str> {
|
pub fn strip_html(html: &str) -> Cow<str> {
|
||||||
|
@ -143,6 +152,10 @@ pub fn strip_html_preserving_image_filenames(html: &str) -> Cow<str> {
|
||||||
without_html.into_owned().into()
|
without_html.into_owned().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn contains_latex(text: &str) -> bool {
|
||||||
|
LATEX.is_match(text)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::text::{
|
use crate::text::{
|
||||||
|
|
Loading…
Reference in a new issue