diff --git a/README.md b/README.md index 2513944..f9dd407 100644 --- a/README.md +++ b/README.md @@ -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: @@ -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" } ``` diff --git a/src/apyanki/anki.py b/src/apyanki/anki.py index fe6d4f3..591bb1b 100644 --- a/src/apyanki/anki.py +++ b/src/apyanki/anki.py @@ -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 @@ -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) diff --git a/src/apyanki/config.py b/src/apyanki/config.py index dedcd94..973a05d 100644 --- a/src/apyanki/config.py +++ b/src/apyanki/config.py @@ -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 diff --git a/src/apyanki/fields.py b/src/apyanki/fields.py index 1964cb3..f37885e 100644 --- a/src/apyanki/fields.py +++ b/src/apyanki/fields.py @@ -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
tags text = text.replace("\n", "
") @@ -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): @@ -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, diff --git a/src/apyanki/note.py b/src/apyanki/note.py index a444d59..bb26a45 100644 --- a/src/apyanki/note.py +++ b/src/apyanki/note.py @@ -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 @@ -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() ] @@ -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) ] @@ -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: @@ -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 diff --git a/tests/test_latex.py b/tests/test_latex.py new file mode 100644 index 0000000..0d286d9 --- /dev/null +++ b/tests/test_latex.py @@ -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]