diff --git a/pylib/anki/media.py b/pylib/anki/media.py index df9097a59..29f67f047 100644 --- a/pylib/anki/media.py +++ b/pylib/anki/media.py @@ -8,10 +8,12 @@ import pprint import re import sys import time -from typing import Any, Callable, List, Optional, Tuple +import urllib.error +import urllib.parse +import urllib.request +from typing import Any, Callable, List, Match, Optional, Tuple from anki import media_pb2 -from anki._legacy import deprecated from anki.consts import * from anki.latex import render_latex, render_latex_returning_errors from anki.models import NotetypeId @@ -183,12 +185,27 @@ class MediaManager: txt = re.sub(reg, "", txt) return txt - @deprecated(info="no longer required") def escapeImages(self, string: str, unescape: bool = False) -> str: - return string + "escape_media_filenames alias for compatibility with add-ons." + return self.escape_media_filenames(string, unescape) - @deprecated(info="no longer required") def escape_media_filenames(self, string: str, unescape: bool = False) -> str: + "Apply or remove percent encoding to filenames in html tags (audio, image, object)." + fn: Callable + if unescape: + fn = urllib.parse.unquote + else: + fn = urllib.parse.quote + + def repl(match: Match) -> str: + tag = match.group(0) + fname = match.group("fname") + if re.match("(https?|ftp)://", fname): + return tag + return tag.replace(fname, fn(fname)) + + for reg in self.html_media_regexps: + string = re.sub(reg, repl, string) return string # Checking media diff --git a/pylib/tests/test_media.py b/pylib/tests/test_media.py index 73dd9bb41..30e2b8056 100644 --- a/pylib/tests/test_media.py +++ b/pylib/tests/test_media.py @@ -49,6 +49,10 @@ def test_strings(): assert sp("aoeu") == "aoeu" assert sp("aoeu[sound:foo.mp3]aoeu") == "aoeuaoeu" assert sp("aoeu") == "aoeu" + es = col.media.escape_media_filenames + assert es("aoeu") == "aoeu" + assert es("") == "" + assert es('') == '' def test_deckIntegration(): diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 21764547d..052833748 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -434,7 +434,10 @@ $editorToolbar.then(({{ toolbar }}) => toolbar.appendGroup({{ if not self.note: return - data = self.note.items() + data = [ + (fld, self.mw.col.media.escape_media_filenames(val)) + for fld, val in self.note.items() + ] self.widget.show() self.updateTags() @@ -584,9 +587,13 @@ $editorToolbar.then(({{ toolbar }}) => toolbar.appendGroup({{ if html.find(">") > -1: # filter html through beautifulsoup so we can strip out things like a # leading + html_escaped = self.mw.col.media.escape_media_filenames(html) with warnings.catch_warnings(): warnings.simplefilter("ignore", UserWarning) - html = str(BeautifulSoup(html, "html.parser")) + html_escaped = str(BeautifulSoup(html_escaped, "html.parser")) + html = self.mw.col.media.escape_media_filenames( + html_escaped, unescape=True + ) self.note.fields[field] = html if not self.addMode: self._save_current_note() @@ -1260,9 +1267,15 @@ def remove_null_bytes(txt: str, editor: Editor) -> str: return txt.replace("\x00", "") +def reverse_url_quoting(txt: str, editor: Editor) -> str: + # reverse the url quoting we added to get images to display + return editor.mw.col.media.escape_media_filenames(txt, unescape=True) + + gui_hooks.editor_will_use_font_for_field.append(fontMungeHack) gui_hooks.editor_will_munge_html.append(munge_html) gui_hooks.editor_will_munge_html.append(remove_null_bytes) +gui_hooks.editor_will_munge_html.append(reverse_url_quoting) def set_cloze_button(editor: Editor) -> None: diff --git a/qt/aqt/main.py b/qt/aqt/main.py index c224a18cb..cdf78b842 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -484,6 +484,7 @@ class AnkiQt(QMainWindow): return aqt.sound.av_refs_to_play_icons(text) def prepare_card_text_for_display(self, text: str) -> str: + text = self.col.media.escape_media_filenames(text) text = self._add_play_buttons(text) return text diff --git a/qt/aqt/utils.py b/qt/aqt/utils.py index e44958c25..c6a983408 100644 --- a/qt/aqt/utils.py +++ b/qt/aqt/utils.py @@ -34,7 +34,6 @@ from PyQt5.QtWidgets import ( ) import aqt -from anki._legacy import deprecated from anki.collection import Collection from anki.lang import TR, tr_legacyglobal # pylint: disable=unused-import from anki.utils import invalidFilename, isMac, isWin, noBundledLibs, versionWithBuild @@ -680,8 +679,9 @@ def restore_combo_history(comboBox: QComboBox, name: str) -> List[str]: return history -@deprecated(info="use mw.prepare_card_text_for_display()") def mungeQA(col: Collection, txt: str) -> str: + print("mungeQA() deprecated; use mw.prepare_card_text_for_display()") + txt = col.media.escape_media_filenames(txt) return txt