From 2c0075162158789682cd95855ffbc747d62cc84f Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison@users.noreply.github.com> Date: Mon, 22 Jan 2024 00:30:26 +0000 Subject: [PATCH] fix: play tags shown in card browser * [sound:foo.mp3] is now [anki:play:a:0] fix: * Define `replaceAvRefsWith` to enable replacements * pass in TemplateRenderContext so we have a reference to the AvTag * allow `stripAvRefs` to accept a formatter override Fixes 14353 --- .../main/java/com/ichi2/anki/CardBrowser.kt | 22 +++++++++---- .../src/main/java/com/ichi2/libanki/Sound.kt | 21 ++++++++++-- .../src/main/java/com/ichi2/libanki/Utils.kt | 9 ------ .../ichi2/anki/CardBrowserNonAndroidTest.kt | 32 ++++++++++++++++--- 4 files changed, 62 insertions(+), 22 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.kt index 6840311d2a9c..7c8d2bb25340 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.kt @@ -2098,8 +2098,8 @@ open class CardBrowser : if (a.startsWith(q)) { a = a.substring(q.length) } - a = formatQA(a, AnkiDroidApp.instance) - q = formatQA(q, AnkiDroidApp.instance) + a = formatQA(a, qa, AnkiDroidApp.instance) + q = formatQA(q, qa, AnkiDroidApp.instance) mQa = Pair(q, a) } @@ -2223,10 +2223,14 @@ open class CardBrowser : fun clearLastDeckId() = SharedPreferencesLastDeckIdRepository.clearLastDeckId() @CheckResult - private fun formatQA(text: String, context: Context): String { + private fun formatQA( + text: String, + qa: TemplateManager.TemplateRenderContext.TemplateRenderOutput, + context: Context + ): String { val showFilenames = context.sharedPrefs().getBoolean("card_browser_show_media_filenames", false) - return formatQAInternal(text, showFilenames) + return formatQAInternal(text, qa, showFilenames) } /** @@ -2236,7 +2240,11 @@ open class CardBrowser : */ @VisibleForTesting @CheckResult - fun formatQAInternal(txt: String, showFileNames: Boolean): String { + fun formatQAInternal( + txt: String, + qa: TemplateManager.TemplateRenderContext.TemplateRenderOutput, + showFileNames: Boolean + ): String { /* Strips all formatting from the string txt for use in displaying question/answer in browser */ var s = txt s = s.replace("".toRegex(), "") @@ -2244,7 +2252,9 @@ open class CardBrowser : s = s.replace("
", " ") s = s.replace("
", " ") s = s.replace("\n", " ") - s = if (showFileNames) Utils.stripSoundMedia(s) else Utils.stripSoundMedia(s, " ") + // we use " " as often users won't leave a space between the '[sound:] tag + // and continuation of the content + s = if (showFileNames) Sound.replaceWithFileNames(s, qa) else stripAvRefs(s, " ") s = s.replace("\\[\\[type:[^]]+]]".toRegex(), "") s = if (showFileNames) Utils.stripHTMLMedia(s) else Utils.stripHTMLMedia(s, " ") s = s.trim { it <= ' ' } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Sound.kt b/AnkiDroid/src/main/java/com/ichi2/libanki/Sound.kt index f783578dbe1b..0c5d20c1e9db 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Sound.kt +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Sound.kt @@ -27,6 +27,7 @@ package com.ichi2.libanki import anki.config.ConfigKey import com.ichi2.anki.CollectionManager +import com.ichi2.libanki.TemplateManager.TemplateRenderContext.TemplateRenderOutput import java.util.regex.Pattern /** @@ -57,7 +58,7 @@ open class AvTag */ val SOUND_RE = Pattern.compile("\\[sound:([^\\[\\]]*)]").toRegex() -fun stripAvRefs(text: String) = Sound.AV_REF_RE.replace(text, "") +fun stripAvRefs(text: String, replacement: String = "") = Sound.AV_REF_RE.replace(text, replacement) // not in libAnki object Sound { @@ -109,7 +110,21 @@ object Sound { */ fun replaceWithSoundTags( content: String, - renderOutput: TemplateManager.TemplateRenderContext.TemplateRenderOutput + renderOutput: TemplateRenderOutput + ): String = replaceAvRefsWith(content, renderOutput) { tag -> "[sound:${tag.filename}]" } + + /** + * Replaces [anki:play:q:0] with ` example.mp3 ` + */ + fun replaceWithFileNames( + content: String, + renderOutput: TemplateRenderOutput + ): String = replaceAvRefsWith(content, renderOutput) { tag -> " ${tag.filename} " } + + private fun replaceAvRefsWith( + content: String, + renderOutput: TemplateRenderOutput, + processTag: (SoundOrVideoTag) -> String ): String { return AV_REF_RE.replace(content) { match -> val groups = match.groupValues @@ -124,7 +139,7 @@ object Sound { if (tag !is SoundOrVideoTag) { return@replace match.value } else { - return@replace "[sound:${tag.filename}]" + return@replace processTag(tag) } } } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Utils.kt b/AnkiDroid/src/main/java/com/ichi2/libanki/Utils.kt index bf47a84f68ea..9a0c3e4eb39a 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Utils.kt +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Utils.kt @@ -41,7 +41,6 @@ object Utils { private val scriptPattern = Pattern.compile("(?si).*?") private val tagPattern = Pattern.compile("(?s)<.*?>") private val imgPattern = Pattern.compile("(?i)]+src=[\"']?([^\"'>]+)[\"']?[^>]*>") - private val soundPattern = Pattern.compile("(?i)\\[sound:([^]]+)]") private val htmlEntitiesPattern = Pattern.compile("&#?\\w+;") private const val ALL_CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" @@ -87,14 +86,6 @@ object Utils { return stripHTML(imgMatcher.replaceAll(replacement)) } - /** - * Strip sound but keep media filenames - */ - fun stripSoundMedia(s: String, replacement: String = " $1 "): String { - val soundMatcher = soundPattern.matcher(s) - return soundMatcher.replaceAll(replacement) - } - /** * Takes a string and replaces all the HTML symbols in it with their unescaped representation. * This should only affect substrings of the form `&something;` and not tags. diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/CardBrowserNonAndroidTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/CardBrowserNonAndroidTest.kt index fd011919e504..bb960d193ada 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/CardBrowserNonAndroidTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/CardBrowserNonAndroidTest.kt @@ -16,6 +16,8 @@ package com.ichi2.anki import androidx.annotation.CheckResult +import com.ichi2.libanki.SoundOrVideoTag +import com.ichi2.libanki.TemplateManager.TemplateRenderContext.TemplateRenderOutput import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.equalTo import org.junit.Test @@ -23,13 +25,13 @@ import org.junit.Test class CardBrowserNonAndroidTest { @Test fun soundIsStrippedCorrectly() { - val output = formatWithFilenamesStripped("aou[sound:foo.mp3]aou") + val output = formatWithFilenamesStripped("aou[anki:play:a:0]aou") assertThat(output, equalTo("aou aou")) } @Test fun soundIsRetainedWithoutTag() { - val output = formatWithFilenamesRetained("aou[sound:foo.mp3]aou") + val output = formatWithFilenamesRetained("aou[anki:play:a:0]aou") assertThat(output, equalTo("aou foo.mp3 aou")) } @@ -47,11 +49,33 @@ class CardBrowserNonAndroidTest { @CheckResult private fun formatWithFilenamesRetained(input: String): String { - return CardBrowser.formatQAInternal(input, true) + return CardBrowser.formatQAInternal( + input, + TemplateRenderOutput( + questionText = input, + answerText = input, + questionAvTags = listOf(SoundOrVideoTag("foo.mp3")), + answerAvTags = listOf(SoundOrVideoTag("foo.mp3")), + css = "" + + ), + true + ) } @CheckResult private fun formatWithFilenamesStripped(input: String): String { - return CardBrowser.formatQAInternal(input, false) + return CardBrowser.formatQAInternal( + input, + TemplateRenderOutput( + questionText = input, + answerText = input, + questionAvTags = listOf(SoundOrVideoTag("foo.mp3")), + answerAvTags = listOf(SoundOrVideoTag("foo.mp3")), + css = "" + + ), + false + ) } }