From 7d0b0e57eeee5d7d958ab6f85cd37fa7f85f66ec Mon Sep 17 00:00:00 2001 From: ByteHamster Date: Fri, 14 Oct 2022 22:25:10 +0200 Subject: Remove text colors from shownotes --- .../antennapod/core/util/gui/ShownotesCleaner.java | 208 ++++++++++++++++++ .../antennapod/core/util/playback/Timeline.java | 196 ----------------- .../core/util/gui/ShownotesCleanerTest.java | 236 +++++++++++++++++++++ .../core/util/playback/TimelineTest.java | 235 -------------------- 4 files changed, 444 insertions(+), 431 deletions(-) create mode 100644 core/src/main/java/de/danoeh/antennapod/core/util/gui/ShownotesCleaner.java delete mode 100644 core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java create mode 100644 core/src/test/java/de/danoeh/antennapod/core/util/gui/ShownotesCleanerTest.java delete mode 100644 core/src/test/java/de/danoeh/antennapod/core/util/playback/TimelineTest.java (limited to 'core') diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/gui/ShownotesCleaner.java b/core/src/main/java/de/danoeh/antennapod/core/util/gui/ShownotesCleaner.java new file mode 100644 index 000000000..dbb2815e2 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/util/gui/ShownotesCleaner.java @@ -0,0 +1,208 @@ +package de.danoeh.antennapod.core.util.gui; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import android.text.TextUtils; +import android.util.Log; +import android.util.TypedValue; + +import androidx.annotation.Nullable; +import org.apache.commons.io.IOUtils; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import de.danoeh.antennapod.core.R; +import de.danoeh.antennapod.core.util.Converter; + +/** + * Cleans up and prepares shownotes: + * - Guesses time stamps to make them clickable + * - Removes some formatting + */ +public class ShownotesCleaner { + private static final String TAG = "Timeline"; + + private static final Pattern TIMECODE_LINK_REGEX = Pattern.compile("antennapod://timecode/(\\d+)"); + private static final String TIMECODE_LINK = "%s"; + private static final Pattern TIMECODE_REGEX = Pattern.compile("\\b((\\d+):)?(\\d+):(\\d{2})\\b"); + private static final Pattern LINE_BREAK_REGEX = Pattern.compile("
"); + private static final String CSS_COLOR = "(?<=(\\s|;|^))color\\s*:([^;])*;"; + private static final String CSS_COMMENT = "/\\*.*?\\*/"; + + private final String rawShownotes; + private final String noShownotesLabel; + private final int playableDuration; + private final String webviewStyle; + + public ShownotesCleaner(Context context, @Nullable String rawShownotes, int playableDuration) { + this.rawShownotes = rawShownotes; + + noShownotesLabel = context.getString(R.string.no_shownotes_label); + this.playableDuration = playableDuration; + final String colorPrimary = colorToHtml(context, android.R.attr.textColorPrimary); + final String colorAccent = colorToHtml(context, R.attr.colorAccent); + final int margin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, + context.getResources().getDisplayMetrics()); + String styleString = ""; + try { + InputStream templateStream = context.getAssets().open("shownotes-style.css"); + styleString = IOUtils.toString(templateStream, "UTF-8"); + } catch (IOException e) { + e.printStackTrace(); + } + webviewStyle = String.format(Locale.US, styleString, colorPrimary, colorAccent, + margin, margin, margin, margin); + } + + private String colorToHtml(Context context, int colorAttr) { + TypedArray res = context.getTheme().obtainStyledAttributes(new int[]{colorAttr}); + @ColorInt int col = res.getColor(0, 0); + final String color = "rgba(" + Color.red(col) + "," + Color.green(col) + "," + + Color.blue(col) + "," + (Color.alpha(col) / 255.0) + ")"; + res.recycle(); + return color; + } + + /** + * Applies an app-specific CSS stylesheet and adds timecode links (optional). + *

+ * This method does NOT change the original shownotes string of the shownotesProvider object and it should + * also not be changed by the caller. + * + * @return The processed HTML string. + */ + @NonNull + public String processShownotes() { + String shownotes = rawShownotes; + + if (TextUtils.isEmpty(shownotes)) { + Log.d(TAG, "shownotesProvider contained no shownotes. Returning 'no shownotes' message"); + shownotes = "

" + noShownotesLabel + "

"; + } + + // replace ASCII line breaks with HTML ones if shownotes don't contain HTML line breaks already + if (!LINE_BREAK_REGEX.matcher(shownotes).find() && !shownotes.contains("

")) { + shownotes = shownotes.replace("\n", "
"); + } + + Document document = Jsoup.parse(shownotes); + cleanCss(document); + document.head().appendElement("style").attr("type", "text/css").text(webviewStyle); + addTimecodes(document); + return document.toString(); + } + + /** + * Returns true if the given link is a timecode link. + */ + public static boolean isTimecodeLink(String link) { + return link != null && link.matches(TIMECODE_LINK_REGEX.pattern()); + } + + /** + * Returns the time in milliseconds that is attached to this link or -1 + * if the link is no valid timecode link. + */ + public static int getTimecodeLinkTime(String link) { + if (isTimecodeLink(link)) { + Matcher m = TIMECODE_LINK_REGEX.matcher(link); + + try { + if (m.find()) { + return Integer.parseInt(m.group(1)); + } + } catch (NumberFormatException e) { + e.printStackTrace(); + } + } + return -1; + } + + private void addTimecodes(Document document) { + Elements elementsWithTimeCodes = document.body().getElementsMatchingOwnText(TIMECODE_REGEX); + Log.d(TAG, "Recognized " + elementsWithTimeCodes.size() + " timecodes"); + + if (elementsWithTimeCodes.size() == 0) { + // No elements with timecodes + return; + } + boolean useHourFormat = true; + + if (playableDuration != Integer.MAX_VALUE) { + + // We need to decide if we are going to treat short timecodes as HH:MM or MM:SS. To do + // so we will parse all the short timecodes and see if they fit in the duration. If one + // does not we will use MM:SS, otherwise all will be parsed as HH:MM. + for (Element element : elementsWithTimeCodes) { + Matcher matcherForElement = TIMECODE_REGEX.matcher(element.html()); + while (matcherForElement.find()) { + + // We only want short timecodes right now. + if (matcherForElement.group(1) == null) { + int time = Converter.durationStringShortToMs(matcherForElement.group(0), true); + + // If the parsed timecode is greater then the duration then we know we need to + // use the minute format so we are done. + if (time > playableDuration) { + useHourFormat = false; + break; + } + } + } + + if (!useHourFormat) { + break; + } + } + } + + for (Element element : elementsWithTimeCodes) { + + Matcher matcherForElement = TIMECODE_REGEX.matcher(element.html()); + StringBuffer buffer = new StringBuffer(); + + while (matcherForElement.find()) { + String group = matcherForElement.group(0); + + int time = matcherForElement.group(1) != null + ? Converter.durationStringLongToMs(group) + : Converter.durationStringShortToMs(group, useHourFormat); + + String replacementText = group; + if (time < playableDuration) { + replacementText = String.format(Locale.US, TIMECODE_LINK, time, group); + } + + matcherForElement.appendReplacement(buffer, replacementText); + } + + matcherForElement.appendTail(buffer); + element.html(buffer.toString()); + } + } + + private void cleanCss(Document document) { + for (Element element : document.getAllElements()) { + if (element.hasAttr("style")) { + element.attr("style", element.attr("style").replaceAll(CSS_COLOR, "")); + } else if (element.tagName().equals("style")) { + element.html(cleanStyleTag(element.html())); + } + } + } + + public static String cleanStyleTag(String oldCss) { + return oldCss.replaceAll(CSS_COMMENT, "").replaceAll(CSS_COLOR, ""); + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java deleted file mode 100644 index e125c7e66..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java +++ /dev/null @@ -1,196 +0,0 @@ -package de.danoeh.antennapod.core.util.playback; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Color; -import androidx.annotation.ColorInt; -import androidx.annotation.NonNull; -import android.text.TextUtils; -import android.util.Log; -import android.util.TypedValue; - -import androidx.annotation.Nullable; -import org.apache.commons.io.IOUtils; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import de.danoeh.antennapod.core.R; -import de.danoeh.antennapod.core.util.Converter; - -/** - * Connects chapter information and shownotes of a shownotesProvider, for example by making it possible to use the - * shownotes to navigate to another position in the podcast or by highlighting certain parts of the shownotesProvider's - * shownotes. - *

- * A timeline object needs a shownotesProvider from which the chapter information - * is retrieved and shownotes are generated. - */ -public class Timeline { - private static final String TAG = "Timeline"; - - private static final Pattern TIMECODE_LINK_REGEX = Pattern.compile("antennapod://timecode/(\\d+)"); - private static final String TIMECODE_LINK = "%s"; - private static final Pattern TIMECODE_REGEX = Pattern.compile("\\b((\\d+):)?(\\d+):(\\d{2})\\b"); - private static final Pattern LINE_BREAK_REGEX = Pattern.compile("
"); - - private final String rawShownotes; - private final String noShownotesLabel; - private final int playableDuration; - private final String webviewStyle; - - public Timeline(Context context, @Nullable String rawShownotes, int playableDuration) { - this.rawShownotes = rawShownotes; - - noShownotesLabel = context.getString(R.string.no_shownotes_label); - this.playableDuration = playableDuration; - final String colorPrimary = colorToHtml(context, android.R.attr.textColorPrimary); - final String colorAccent = colorToHtml(context, R.attr.colorAccent); - final int margin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, - context.getResources().getDisplayMetrics()); - String styleString = ""; - try { - InputStream templateStream = context.getAssets().open("shownotes-style.css"); - styleString = IOUtils.toString(templateStream, "UTF-8"); - } catch (IOException e) { - e.printStackTrace(); - } - webviewStyle = String.format(Locale.US, styleString, colorPrimary, colorAccent, - margin, margin, margin, margin); - } - - private String colorToHtml(Context context, int colorAttr) { - TypedArray res = context.getTheme().obtainStyledAttributes(new int[]{colorAttr}); - @ColorInt int col = res.getColor(0, 0); - final String color = "rgba(" + Color.red(col) + "," + Color.green(col) + "," - + Color.blue(col) + "," + (Color.alpha(col) / 255.0) + ")"; - res.recycle(); - return color; - } - - /** - * Applies an app-specific CSS stylesheet and adds timecode links (optional). - *

- * This method does NOT change the original shownotes string of the shownotesProvider object and it should - * also not be changed by the caller. - * - * @return The processed HTML string. - */ - @NonNull - public String processShownotes() { - String shownotes = rawShownotes; - - if (TextUtils.isEmpty(shownotes)) { - Log.d(TAG, "shownotesProvider contained no shownotes. Returning 'no shownotes' message"); - shownotes = "

" + noShownotesLabel + "

"; - } - - // replace ASCII line breaks with HTML ones if shownotes don't contain HTML line breaks already - if (!LINE_BREAK_REGEX.matcher(shownotes).find() && !shownotes.contains("

")) { - shownotes = shownotes.replace("\n", "
"); - } - - Document document = Jsoup.parse(shownotes); - document.head().appendElement("style").attr("type", "text/css").text(webviewStyle); - - // apply timecode links - addTimecodes(document); - return document.toString(); - } - - /** - * Returns true if the given link is a timecode link. - */ - public static boolean isTimecodeLink(String link) { - return link != null && link.matches(TIMECODE_LINK_REGEX.pattern()); - } - - /** - * Returns the time in milliseconds that is attached to this link or -1 - * if the link is no valid timecode link. - */ - public static int getTimecodeLinkTime(String link) { - if (isTimecodeLink(link)) { - Matcher m = TIMECODE_LINK_REGEX.matcher(link); - - try { - if (m.find()) { - return Integer.parseInt(m.group(1)); - } - } catch (NumberFormatException e) { - e.printStackTrace(); - } - } - return -1; - } - - private void addTimecodes(Document document) { - Elements elementsWithTimeCodes = document.body().getElementsMatchingOwnText(TIMECODE_REGEX); - Log.d(TAG, "Recognized " + elementsWithTimeCodes.size() + " timecodes"); - - if (elementsWithTimeCodes.size() == 0) { - // No elements with timecodes - return; - } - boolean useHourFormat = true; - - if (playableDuration != Integer.MAX_VALUE) { - - // We need to decide if we are going to treat short timecodes as HH:MM or MM:SS. To do - // so we will parse all the short timecodes and see if they fit in the duration. If one - // does not we will use MM:SS, otherwise all will be parsed as HH:MM. - for (Element element : elementsWithTimeCodes) { - Matcher matcherForElement = TIMECODE_REGEX.matcher(element.html()); - while (matcherForElement.find()) { - - // We only want short timecodes right now. - if (matcherForElement.group(1) == null) { - int time = Converter.durationStringShortToMs(matcherForElement.group(0), true); - - // If the parsed timecode is greater then the duration then we know we need to - // use the minute format so we are done. - if (time > playableDuration) { - useHourFormat = false; - break; - } - } - } - - if (!useHourFormat) { - break; - } - } - } - - for (Element element : elementsWithTimeCodes) { - - Matcher matcherForElement = TIMECODE_REGEX.matcher(element.html()); - StringBuffer buffer = new StringBuffer(); - - while (matcherForElement.find()) { - String group = matcherForElement.group(0); - - int time = matcherForElement.group(1) != null - ? Converter.durationStringLongToMs(group) - : Converter.durationStringShortToMs(group, useHourFormat); - - String replacementText = group; - if (time < playableDuration) { - replacementText = String.format(Locale.US, TIMECODE_LINK, time, group); - } - - matcherForElement.appendReplacement(buffer, replacementText); - } - - matcherForElement.appendTail(buffer); - element.html(buffer.toString()); - } - } -} diff --git a/core/src/test/java/de/danoeh/antennapod/core/util/gui/ShownotesCleanerTest.java b/core/src/test/java/de/danoeh/antennapod/core/util/gui/ShownotesCleanerTest.java new file mode 100644 index 000000000..527d0e0a3 --- /dev/null +++ b/core/src/test/java/de/danoeh/antennapod/core/util/gui/ShownotesCleanerTest.java @@ -0,0 +1,236 @@ +package de.danoeh.antennapod.core.util.gui; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Test class for {@link ShownotesCleaner}. + */ +@RunWith(RobolectricTestRunner.class) +public class ShownotesCleanerTest { + + private Context context; + + @Before + public void setUp() { + context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + } + + @Test + public void testProcessShownotesAddTimecodeHhmmssNoChapters() { + final String timeStr = "10:11:12"; + final long time = 3600 * 1000 * 10 + 60 * 1000 * 11 + 12 * 1000; + + String shownotes = "

Some test text with a timecode " + timeStr + " here.

"; + ShownotesCleaner t = new ShownotesCleaner(context, shownotes, Integer.MAX_VALUE); + String res = t.processShownotes(); + checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); + } + + @Test + public void testProcessShownotesAddTimecodeHhmmssMoreThen24HoursNoChapters() { + final String timeStr = "25:00:00"; + final long time = 25 * 60 * 60 * 1000; + + String shownotes = "

Some test text with a timecode " + timeStr + " here.

"; + ShownotesCleaner t = new ShownotesCleaner(context, shownotes, Integer.MAX_VALUE); + String res = t.processShownotes(); + checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); + } + + @Test + public void testProcessShownotesAddTimecodeHhmmNoChapters() { + final String timeStr = "10:11"; + final long time = 3600 * 1000 * 10 + 60 * 1000 * 11; + + String shownotes = "

Some test text with a timecode " + timeStr + " here.

"; + ShownotesCleaner t = new ShownotesCleaner(context, shownotes, Integer.MAX_VALUE); + String res = t.processShownotes(); + checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); + } + + @Test + public void testProcessShownotesAddTimecodeMmssNoChapters() { + final String timeStr = "10:11"; + final long time = 10 * 60 * 1000 + 11 * 1000; + + String shownotes = "

Some test text with a timecode " + timeStr + " here.

"; + ShownotesCleaner t = new ShownotesCleaner(context, shownotes, 11 * 60 * 1000); + String res = t.processShownotes(); + checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); + } + + @Test + public void testProcessShownotesAddTimecodeHmmssNoChapters() { + final String timeStr = "2:11:12"; + final long time = 2 * 60 * 60 * 1000 + 11 * 60 * 1000 + 12 * 1000; + + String shownotes = "

Some test text with a timecode " + timeStr + " here.

"; + ShownotesCleaner t = new ShownotesCleaner(context, shownotes, Integer.MAX_VALUE); + String res = t.processShownotes(); + checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); + } + + @Test + public void testProcessShownotesAddTimecodeMssNoChapters() { + final String timeStr = "1:12"; + final long time = 60 * 1000 + 12 * 1000; + + String shownotes = "

Some test text with a timecode " + timeStr + " here.

"; + ShownotesCleaner t = new ShownotesCleaner(context, shownotes, 2 * 60 * 1000); + String res = t.processShownotes(); + checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); + } + + @Test + public void testProcessShownotesAddNoTimecodeDuration() { + final String timeStr = "2:11:12"; + final int time = 2 * 60 * 60 * 1000 + 11 * 60 * 1000 + 12 * 1000; + + String shownotes = "

Some test text with a timecode " + timeStr + " here.

"; + ShownotesCleaner t = new ShownotesCleaner(context, shownotes, time); + String res = t.processShownotes(); + Document d = Jsoup.parse(res); + assertEquals("Should not parse time codes that equal duration", 0, d.body().getElementsByTag("a").size()); + } + + @Test + public void testProcessShownotesAddTimecodeMultipleFormatsNoChapters() { + final String[] timeStrings = new String[]{ "10:12", "1:10:12" }; + + String shownotes = "

Some test text with a timecode " + timeStrings[0] + + " here. Hey look another one " + timeStrings[1] + " here!

"; + ShownotesCleaner t = new ShownotesCleaner(context, shownotes, 2 * 60 * 60 * 1000); + String res = t.processShownotes(); + checkLinkCorrect(res, new long[]{10 * 60 * 1000 + 12 * 1000, + 60 * 60 * 1000 + 10 * 60 * 1000 + 12 * 1000}, timeStrings); + } + + @Test + public void testProcessShownotesAddTimecodeMultipleShortFormatNoChapters() { + + // One of these timecodes fits as HH:MM and one does not so both should be parsed as MM:SS. + final String[] timeStrings = new String[]{ "10:12", "2:12" }; + + String shownotes = "

Some test text with a timecode " + timeStrings[0] + + " here. Hey look another one " + timeStrings[1] + " here!

"; + ShownotesCleaner t = new ShownotesCleaner(context, shownotes, 3 * 60 * 60 * 1000); + String res = t.processShownotes(); + checkLinkCorrect(res, new long[]{10 * 60 * 1000 + 12 * 1000, 2 * 60 * 1000 + 12 * 1000}, timeStrings); + } + + @Test + public void testProcessShownotesAddTimecodeParentheses() { + final String timeStr = "10:11"; + final long time = 3600 * 1000 * 10 + 60 * 1000 * 11; + + String shownotes = "

Some test text with a timecode (" + timeStr + ") here.

"; + ShownotesCleaner t = new ShownotesCleaner(context, shownotes, Integer.MAX_VALUE); + String res = t.processShownotes(); + checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); + } + + @Test + public void testProcessShownotesAddTimecodeBrackets() { + final String timeStr = "10:11"; + final long time = 3600 * 1000 * 10 + 60 * 1000 * 11; + + String shownotes = "

Some test text with a timecode [" + timeStr + "] here.

"; + ShownotesCleaner t = new ShownotesCleaner(context, shownotes, Integer.MAX_VALUE); + String res = t.processShownotes(); + checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); + } + + @Test + public void testProcessShownotesAddTimecodeAngleBrackets() { + final String timeStr = "10:11"; + final long time = 3600 * 1000 * 10 + 60 * 1000 * 11; + + String shownotes = "

Some test text with a timecode <" + timeStr + "> here.

"; + ShownotesCleaner t = new ShownotesCleaner(context, shownotes, Integer.MAX_VALUE); + String res = t.processShownotes(); + checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); + } + + @Test + public void testProcessShownotesAndInvalidTimecode() { + final String[] timeStrs = new String[] {"2:1", "0:0", "000", "00", "00:000"}; + + StringBuilder shownotes = new StringBuilder("

Some test text with timecodes "); + for (String timeStr : timeStrs) { + shownotes.append(timeStr).append(" "); + } + shownotes.append("here.

"); + + ShownotesCleaner t = new ShownotesCleaner(context, shownotes.toString(), Integer.MAX_VALUE); + String res = t.processShownotes(); + checkLinkCorrect(res, new long[0], new String[0]); + } + + private void checkLinkCorrect(String res, long[] timecodes, String[] timecodeStr) { + assertNotNull(res); + Document d = Jsoup.parse(res); + Elements links = d.body().getElementsByTag("a"); + int countedLinks = 0; + for (Element link : links) { + String href = link.attributes().get("href"); + String text = link.text(); + if (href.startsWith("antennapod://")) { + assertTrue(href.endsWith(String.valueOf(timecodes[countedLinks]))); + assertEquals(timecodeStr[countedLinks], text); + countedLinks++; + assertTrue("Contains too many links: " + countedLinks + " > " + + timecodes.length, countedLinks <= timecodes.length); + } + } + assertEquals(timecodes.length, countedLinks); + } + + @Test + public void testIsTimecodeLink() { + assertFalse(ShownotesCleaner.isTimecodeLink(null)); + assertFalse(ShownotesCleaner.isTimecodeLink("http://antennapod/timecode/123123")); + assertFalse(ShownotesCleaner.isTimecodeLink("antennapod://timecode/")); + assertFalse(ShownotesCleaner.isTimecodeLink("antennapod://123123")); + assertFalse(ShownotesCleaner.isTimecodeLink("antennapod://timecode/123123a")); + assertTrue(ShownotesCleaner.isTimecodeLink("antennapod://timecode/123")); + assertTrue(ShownotesCleaner.isTimecodeLink("antennapod://timecode/1")); + } + + @Test + public void testGetTimecodeLinkTime() { + assertEquals(-1, ShownotesCleaner.getTimecodeLinkTime(null)); + assertEquals(-1, ShownotesCleaner.getTimecodeLinkTime("http://timecode/123")); + assertEquals(123, ShownotesCleaner.getTimecodeLinkTime("antennapod://timecode/123")); + } + + @Test + public void testCleanupColors() { + final String input = "/* /* */ .foo { text-decoration: underline;color:#f00;font-weight:bold;}" + + "#bar { text-decoration: underline;color:#f00;font-weight:bold; }" + + "div {text-decoration: underline; color /* */ : /* */ #f00 /* */; font-weight:bold; }" + + "#foobar { /* color: */ text-decoration: underline; /* color: */font-weight:bold /* ; */; }" + + "baz { background-color:#f00;border: solid 2px;border-color:#0f0;text-decoration: underline; }"; + final String expected = " .foo { text-decoration: underline;font-weight:bold;}" + + "#bar { text-decoration: underline;font-weight:bold; }" + + "div {text-decoration: underline; font-weight:bold; }" + + "#foobar { text-decoration: underline; font-weight:bold ; }" + + "baz { background-color:#f00;border: solid 2px;border-color:#0f0;text-decoration: underline; }"; + assertEquals(expected, ShownotesCleaner.cleanStyleTag(input)); + } +} diff --git a/core/src/test/java/de/danoeh/antennapod/core/util/playback/TimelineTest.java b/core/src/test/java/de/danoeh/antennapod/core/util/playback/TimelineTest.java deleted file mode 100644 index 987a75981..000000000 --- a/core/src/test/java/de/danoeh/antennapod/core/util/playback/TimelineTest.java +++ /dev/null @@ -1,235 +0,0 @@ -package de.danoeh.antennapod.core.util.playback; - -import android.content.Context; - -import androidx.test.platform.app.InstrumentationRegistry; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; -import de.danoeh.antennapod.core.storage.DBReader; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.robolectric.RobolectricTestRunner; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -/** - * Test class for {@link Timeline}. - */ -@RunWith(RobolectricTestRunner.class) -public class TimelineTest { - - private Context context; - MockedStatic dbReaderMock; - - @Before - public void setUp() { - context = InstrumentationRegistry.getInstrumentation().getTargetContext(); - // mock DBReader, because Timeline.processShownotes() calls FeedItem.loadShownotes() - // which calls DBReader.loadDescriptionOfFeedItem(), but we don't need the database here - dbReaderMock = Mockito.mockStatic(DBReader.class); - } - - @After - public void tearDown() { - dbReaderMock.close(); - } - - @Test - public void testProcessShownotesAddTimecodeHhmmssNoChapters() { - final String timeStr = "10:11:12"; - final long time = 3600 * 1000 * 10 + 60 * 1000 * 11 + 12 * 1000; - - String shownotes = "

Some test text with a timecode " + timeStr + " here.

"; - Timeline t = new Timeline(context, shownotes, Integer.MAX_VALUE); - String res = t.processShownotes(); - checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); - } - - @Test - public void testProcessShownotesAddTimecodeHhmmssMoreThen24HoursNoChapters() { - final String timeStr = "25:00:00"; - final long time = 25 * 60 * 60 * 1000; - - String shownotes = "

Some test text with a timecode " + timeStr + " here.

"; - Timeline t = new Timeline(context, shownotes, Integer.MAX_VALUE); - String res = t.processShownotes(); - checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); - } - - @Test - public void testProcessShownotesAddTimecodeHhmmNoChapters() { - final String timeStr = "10:11"; - final long time = 3600 * 1000 * 10 + 60 * 1000 * 11; - - String shownotes = "

Some test text with a timecode " + timeStr + " here.

"; - Timeline t = new Timeline(context, shownotes, Integer.MAX_VALUE); - String res = t.processShownotes(); - checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); - } - - @Test - public void testProcessShownotesAddTimecodeMmssNoChapters() { - final String timeStr = "10:11"; - final long time = 10 * 60 * 1000 + 11 * 1000; - - String shownotes = "

Some test text with a timecode " + timeStr + " here.

"; - Timeline t = new Timeline(context, shownotes, 11 * 60 * 1000); - String res = t.processShownotes(); - checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); - } - - @Test - public void testProcessShownotesAddTimecodeHmmssNoChapters() { - final String timeStr = "2:11:12"; - final long time = 2 * 60 * 60 * 1000 + 11 * 60 * 1000 + 12 * 1000; - - String shownotes = "

Some test text with a timecode " + timeStr + " here.

"; - Timeline t = new Timeline(context, shownotes, Integer.MAX_VALUE); - String res = t.processShownotes(); - checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); - } - - @Test - public void testProcessShownotesAddTimecodeMssNoChapters() { - final String timeStr = "1:12"; - final long time = 60 * 1000 + 12 * 1000; - - String shownotes = "

Some test text with a timecode " + timeStr + " here.

"; - Timeline t = new Timeline(context, shownotes, 2 * 60 * 1000); - String res = t.processShownotes(); - checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); - } - - @Test - public void testProcessShownotesAddNoTimecodeDuration() { - final String timeStr = "2:11:12"; - final int time = 2 * 60 * 60 * 1000 + 11 * 60 * 1000 + 12 * 1000; - - String shownotes = "

Some test text with a timecode " + timeStr + " here.

"; - Timeline t = new Timeline(context, shownotes, time); - String res = t.processShownotes(); - Document d = Jsoup.parse(res); - assertEquals("Should not parse time codes that equal duration", 0, d.body().getElementsByTag("a").size()); - } - - @Test - public void testProcessShownotesAddTimecodeMultipleFormatsNoChapters() { - final String[] timeStrings = new String[]{ "10:12", "1:10:12" }; - - String shownotes = "

Some test text with a timecode " + timeStrings[0] - + " here. Hey look another one " + timeStrings[1] + " here!

"; - Timeline t = new Timeline(context, shownotes, 2 * 60 * 60 * 1000); - String res = t.processShownotes(); - checkLinkCorrect(res, new long[]{10 * 60 * 1000 + 12 * 1000, - 60 * 60 * 1000 + 10 * 60 * 1000 + 12 * 1000}, timeStrings); - } - - @Test - public void testProcessShownotesAddTimecodeMultipleShortFormatNoChapters() { - - // One of these timecodes fits as HH:MM and one does not so both should be parsed as MM:SS. - final String[] timeStrings = new String[]{ "10:12", "2:12" }; - - String shownotes = "

Some test text with a timecode " + timeStrings[0] - + " here. Hey look another one " + timeStrings[1] + " here!

"; - Timeline t = new Timeline(context, shownotes, 3 * 60 * 60 * 1000); - String res = t.processShownotes(); - checkLinkCorrect(res, new long[]{10 * 60 * 1000 + 12 * 1000, 2 * 60 * 1000 + 12 * 1000}, timeStrings); - } - - @Test - public void testProcessShownotesAddTimecodeParentheses() { - final String timeStr = "10:11"; - final long time = 3600 * 1000 * 10 + 60 * 1000 * 11; - - String shownotes = "

Some test text with a timecode (" + timeStr + ") here.

"; - Timeline t = new Timeline(context, shownotes, Integer.MAX_VALUE); - String res = t.processShownotes(); - checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); - } - - @Test - public void testProcessShownotesAddTimecodeBrackets() { - final String timeStr = "10:11"; - final long time = 3600 * 1000 * 10 + 60 * 1000 * 11; - - String shownotes = "

Some test text with a timecode [" + timeStr + "] here.

"; - Timeline t = new Timeline(context, shownotes, Integer.MAX_VALUE); - String res = t.processShownotes(); - checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); - } - - @Test - public void testProcessShownotesAddTimecodeAngleBrackets() { - final String timeStr = "10:11"; - final long time = 3600 * 1000 * 10 + 60 * 1000 * 11; - - String shownotes = "

Some test text with a timecode <" + timeStr + "> here.

"; - Timeline t = new Timeline(context, shownotes, Integer.MAX_VALUE); - String res = t.processShownotes(); - checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); - } - - @Test - public void testProcessShownotesAndInvalidTimecode() { - final String[] timeStrs = new String[] {"2:1", "0:0", "000", "00", "00:000"}; - - StringBuilder shownotes = new StringBuilder("

Some test text with timecodes "); - for (String timeStr : timeStrs) { - shownotes.append(timeStr).append(" "); - } - shownotes.append("here.

"); - - Timeline t = new Timeline(context, shownotes.toString(), Integer.MAX_VALUE); - String res = t.processShownotes(); - checkLinkCorrect(res, new long[0], new String[0]); - } - - private void checkLinkCorrect(String res, long[] timecodes, String[] timecodeStr) { - assertNotNull(res); - Document d = Jsoup.parse(res); - Elements links = d.body().getElementsByTag("a"); - int countedLinks = 0; - for (Element link : links) { - String href = link.attributes().get("href"); - String text = link.text(); - if (href.startsWith("antennapod://")) { - assertTrue(href.endsWith(String.valueOf(timecodes[countedLinks]))); - assertEquals(timecodeStr[countedLinks], text); - countedLinks++; - assertTrue("Contains too many links: " + countedLinks + " > " - + timecodes.length, countedLinks <= timecodes.length); - } - } - assertEquals(timecodes.length, countedLinks); - } - - @Test - public void testIsTimecodeLink() { - assertFalse(Timeline.isTimecodeLink(null)); - assertFalse(Timeline.isTimecodeLink("http://antennapod/timecode/123123")); - assertFalse(Timeline.isTimecodeLink("antennapod://timecode/")); - assertFalse(Timeline.isTimecodeLink("antennapod://123123")); - assertFalse(Timeline.isTimecodeLink("antennapod://timecode/123123a")); - assertTrue(Timeline.isTimecodeLink("antennapod://timecode/123")); - assertTrue(Timeline.isTimecodeLink("antennapod://timecode/1")); - } - - @Test - public void testGetTimecodeLinkTime() { - assertEquals(-1, Timeline.getTimecodeLinkTime(null)); - assertEquals(-1, Timeline.getTimecodeLinkTime("http://timecode/123")); - assertEquals(123, Timeline.getTimecodeLinkTime("antennapod://timecode/123")); - - } -} -- cgit v1.2.3