Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ currently recognized:
- `query`: Specify default query for `apy list`, `apy review` and `apy tag`.
- `review_show_cards`: Whether to show list of cards by default during note
review
- `latexTranslateMode`: Specify which characters should be used as latex delimiters.
The following values are recognized:
- `off`: Default behaviour (block: `\[`, `\]` and inline: `\(`,`\)`)
- `mathjax`: common markdown syntax (block: `$$`, `$$` and inline: `$`,`$`)
- `latex`: latex behaviour (block: `[$$]` `[/$$]` and inline: `[$]`,`[$/]`)

An example configuration:

Expand All @@ -151,7 +156,8 @@ An example configuration:
["pdfcrop", "tmp.pdf", "tmp.pdf"],
["pdf2svg", "tmp.pdf", "tmp.svg"]
],
"review_show_cards": true
"review_show_cards": true,
"latexTranslateMode": "off"
}
```

Expand Down
5 changes: 4 additions & 1 deletion src/apyanki/anki.py
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,7 @@ def add_notes_single(
tags: str = "",
model_name_in: str | None = None,
deck: str | None = None,
latexTranslateMode: str | None = None,
) -> Note:
"""Add new note to collection from args"""
model_name: str
Expand All @@ -677,5 +678,7 @@ def add_notes_single(
field_names: list[str] = [field["name"] for field in model["flds"]]
fields = dict(zip(field_names, field_values))

new_note = NoteData(model_name, tags, fields, markdown, deck)
new_note = NoteData(
model_name, tags, fields, markdown, deck, latexMode=latexTranslateMode
)
return new_note.add_to_collection(self)
1 change: 1 addition & 0 deletions src/apyanki/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def get_base_path() -> str | None:
"profile_name": None,
"query": "tag:marked OR -flag:0",
"review_show_cards": False,
"latexTranslateMode": "off",
}

# Ensure that cfg has required keys
Expand Down
56 changes: 48 additions & 8 deletions src/apyanki/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,12 @@ def convert_field_to_text(field: str, check_consistency: bool = True) -> str:
return text.strip()


def convert_text_to_field(text: str, use_markdown: bool) -> str:
def convert_text_to_field(
text: str, use_markdown: bool, latexMode: str | None = None
) -> str:
"""Convert text to Anki field html."""
if use_markdown:
return _convert_markdown_to_field(text)
return _convert_markdown_to_field(text, latexMode=latexMode)

# Convert newlines to <br> tags
text = text.replace("\n", "<br />")
Expand Down Expand Up @@ -209,7 +211,7 @@ def _convert_field_to_markdown(field: str, check_consistency: bool = False) -> s
return text


def _convert_markdown_to_field(text: str) -> str:
def _convert_markdown_to_field(text: str, latexMode: str | None = None) -> str:
"""Convert Markdown to field HTML"""
# Don't convert if md text is really plain
if re.match(r"[a-zA-Z0-9æøåÆØÅ ,.?+-]*$", text):
Expand All @@ -230,11 +232,49 @@ def _convert_markdown_to_field(text: str) -> str:
# Fix whitespaces in input
text = text.replace("\xc2\xa0", " ").replace("\xa0", " ")

# For convenience: Fix mathjax escaping
text = text.replace(r"\[", r"\\[")
text = text.replace(r"\]", r"\\]")
text = text.replace(r"\(", r"\\(")
text = text.replace(r"\)", r"\\)")
# get the correct LatexTranslateMode
if not latexMode:
latexMode = cfg["latexTranslateMode"]

# default behaviour
if latexMode == "off":
text = text.replace(r"\[", r"\\[")
text = text.replace(r"\]", r"\\]")
text = text.replace(r"\(", r"\\(")
text = text.replace(r"\)", r"\\)")
elif latexMode == "mathjax":
# blocks
subs: list[str] = text.split("$$")
text = ""
open: bool = False

for sub in subs[:-1]:
if open:
text += sub + r"\\]"
else:
text += sub + r"\\["
open = not open
text += subs[-1]

# inline
subs = text.split("$")
text = ""
open = False

for sub in subs[:-1]:
if open:
text += sub + r"\\)"
else:
text += sub + r"\\("
open = not open

text += subs[-1]

elif latexMode == "latex":
text = text.replace(r"[$$]", r"\\[")
text = text.replace(r"[/$$]", r"\\]")
text = text.replace(r"[$]", r"\\(")
text = text.replace(r"[$/]", r"\\)")

html = markdown.markdown(
text,
Expand Down
13 changes: 12 additions & 1 deletion src/apyanki/note.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,7 @@ class NoteData:
deck: str | None = None
nid: str | None = None
cid: str | None = None
latexMode: str | None = None

def add_to_collection(self, anki: Anki) -> Note:
"""Add note to collection
Expand All @@ -600,7 +601,9 @@ def add_to_collection(self, anki: Anki) -> Note:
note_type["did"] = anki.deck_name_to_id[self.deck]

new_note.fields = [
convert_text_to_field(f, use_markdown=self.markdown)
convert_text_to_field(
f, use_markdown=self.markdown, latexMode=self.latexMode
)
for f in self.fields.values()
]

Expand Down Expand Up @@ -736,6 +739,7 @@ def markdown_file_to_notes(filename: str) -> list[NoteData]:
deck=x["deck"],
nid=x["nid"],
cid=x["cid"],
latexMode=x["latexTranslateMode"],
)
for x in _parse_markdown_file(filename)
]
Expand All @@ -759,6 +763,7 @@ def _parse_markdown_file(filename: str) -> list[dict[str, Any]]:
"deck": None,
"nid": None,
"cid": None,
"latexTranslateMode": None,
}
with open(filename, "r", encoding="utf8") as f:
for line in f:
Expand All @@ -779,6 +784,12 @@ def _parse_markdown_file(filename: str) -> list[dict[str, Any]]:
defaults["nid"] = v
elif k == "cid":
defaults["cid"] = v
elif k in ("latextranslatemode", "latexmode") and v in (
"off",
"mathjax",
"latex",
):
defaults["latexTranslateMode"] = v
else:
defaults[k] = v

Expand Down
40 changes: 40 additions & 0 deletions tests/test_latex.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from common import AnkiEmpty


def test_latexTranslateMode_off_simple():
"""Simple text replacement test for off option"""
with AnkiEmpty() as a:
note = a.add_notes_single(
["This is \\[block\\] math.", "This is \\(inline\\) math."],
markdown=True,
latexTranslateMode="off",
)
assert "data-original-markdown" in note.n.fields[0]
assert "\\[block\\]" in note.n.fields[0]
assert "\\(inline\\)" in note.n.fields[1]


def test_latexTranslateMode_mathjax_simple():
"""Simple text replacement test for mathjax option"""
with AnkiEmpty() as a:
note = a.add_notes_single(
["This is $$block$$ math.", "This is $inline$ math."],
markdown=True,
latexTranslateMode="mathjax",
)
assert "data-original-markdown" in note.n.fields[0]
assert "\\[block\\]" in note.n.fields[0]
assert "\\(inline\\)" in note.n.fields[1]


def test_latexTranslateMode_latex_simple():
"""Simple text replacement test for latex option"""
with AnkiEmpty() as a:
note = a.add_notes_single(
["This is [$$]block[/$$] math.", "This is [$]inline[$/] math."],
markdown=True,
latexTranslateMode="latex",
)
assert "data-original-markdown" in note.n.fields[0]
assert "\\[block\\]" in note.n.fields[0]
assert "\\(inline\\)" in note.n.fields[1]
Loading