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
+ )
}
}