Remove text colors from shownotes

This commit is contained in:
ByteHamster 2022-10-14 22:25:10 +02:00
parent 504002c48f
commit 7d0b0e57ee
5 changed files with 76 additions and 62 deletions

View File

@ -14,7 +14,7 @@ import androidx.fragment.app.Fragment;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.playback.PlaybackController;
import de.danoeh.antennapod.core.util.playback.Timeline;
import de.danoeh.antennapod.core.util.gui.ShownotesCleaner;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.model.playback.Playable;
import de.danoeh.antennapod.view.ShownotesWebView;
@ -99,8 +99,9 @@ public class ItemDescriptionFragment extends Fragment {
}
DBReader.loadDescriptionOfFeedItem(feedMedia.getItem());
}
Timeline timeline = new Timeline(getActivity(), media.getDescription(), media.getDuration());
emitter.onSuccess(timeline.processShownotes());
ShownotesCleaner shownotesCleaner = new ShownotesCleaner(
getActivity(), media.getDescription(), media.getDuration());
emitter.onSuccess(shownotesCleaner.processShownotes());
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())

View File

@ -58,7 +58,7 @@ import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.ui.common.CircularProgressBar;
import de.danoeh.antennapod.ui.common.ThemeUtils;
import de.danoeh.antennapod.core.util.playback.PlaybackController;
import de.danoeh.antennapod.core.util.playback.Timeline;
import de.danoeh.antennapod.core.util.gui.ShownotesCleaner;
import de.danoeh.antennapod.view.ShownotesWebView;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
@ -438,7 +438,7 @@ public class ItemFragment extends Fragment {
if (feedItem != null && context != null) {
int duration = feedItem.getMedia() != null ? feedItem.getMedia().getDuration() : Integer.MAX_VALUE;
DBReader.loadDescriptionOfFeedItem(feedItem);
Timeline t = new Timeline(context, feedItem.getDescription(), duration);
ShownotesCleaner t = new ShownotesCleaner(context, feedItem.getDescription(), duration);
webviewData = t.processShownotes();
}
return feedItem;

View File

@ -30,7 +30,7 @@ import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.core.util.ShareUtils;
import de.danoeh.antennapod.core.util.playback.Timeline;
import de.danoeh.antennapod.core.util.gui.ShownotesCleaner;
public class ShownotesWebView extends WebView implements View.OnLongClickListener {
private static final String TAG = "ShownotesWebView";
@ -73,8 +73,8 @@ public class ShownotesWebView extends WebView implements View.OnLongClickListene
setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (Timeline.isTimecodeLink(url) && timecodeSelectedListener != null) {
timecodeSelectedListener.accept(Timeline.getTimecodeLinkTime(url));
if (ShownotesCleaner.isTimecodeLink(url) && timecodeSelectedListener != null) {
timecodeSelectedListener.accept(ShownotesCleaner.getTimecodeLinkTime(url));
} else {
IntentUtils.openInBrowser(getContext(), url);
}
@ -137,8 +137,8 @@ public class ShownotesWebView extends WebView implements View.OnLongClickListene
ViewCompat.setElevation(s.getView(), 100);
s.show();
} else if (itemId == R.id.go_to_position_item) {
if (Timeline.isTimecodeLink(selectedUrl) && timecodeSelectedListener != null) {
timecodeSelectedListener.accept(Timeline.getTimecodeLinkTime(selectedUrl));
if (ShownotesCleaner.isTimecodeLink(selectedUrl) && timecodeSelectedListener != null) {
timecodeSelectedListener.accept(ShownotesCleaner.getTimecodeLinkTime(selectedUrl));
} else {
Log.e(TAG, "Selected go_to_position_item, but URL was no timecode link: " + selectedUrl);
}
@ -157,9 +157,9 @@ public class ShownotesWebView extends WebView implements View.OnLongClickListene
return;
}
if (Timeline.isTimecodeLink(selectedUrl)) {
if (ShownotesCleaner.isTimecodeLink(selectedUrl)) {
menu.add(Menu.NONE, R.id.go_to_position_item, Menu.NONE, R.string.go_to_position_label);
menu.setHeaderTitle(Converter.getDurationStringLong(Timeline.getTimecodeLinkTime(selectedUrl)));
menu.setHeaderTitle(Converter.getDurationStringLong(ShownotesCleaner.getTimecodeLinkTime(selectedUrl)));
} else {
Uri uri = Uri.parse(selectedUrl);
final Intent intent = new Intent(Intent.ACTION_VIEW, uri);

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.util.playback;
package de.danoeh.antennapod.core.util.gui;
import android.content.Context;
import android.content.res.TypedArray;
@ -26,27 +26,26 @@ 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.
* <p/>
* A timeline object needs a shownotesProvider from which the chapter information
* is retrieved and shownotes are generated.
* Cleans up and prepares shownotes:
* - Guesses time stamps to make them clickable
* - Removes some formatting
*/
public class Timeline {
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 = "<a class=\"timecode\" href=\"antennapod://timecode/%d\">%s</a>";
private static final Pattern TIMECODE_REGEX = Pattern.compile("\\b((\\d+):)?(\\d+):(\\d{2})\\b");
private static final Pattern LINE_BREAK_REGEX = Pattern.compile("<br */?>");
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 Timeline(Context context, @Nullable String rawShownotes, int playableDuration) {
public ShownotesCleaner(Context context, @Nullable String rawShownotes, int playableDuration) {
this.rawShownotes = rawShownotes;
noShownotesLabel = context.getString(R.string.no_shownotes_label);
@ -98,9 +97,8 @@ public class Timeline {
}
Document document = Jsoup.parse(shownotes);
cleanCss(document);
document.head().appendElement("style").attr("type", "text/css").text(webviewStyle);
// apply timecode links
addTimecodes(document);
return document.toString();
}
@ -193,4 +191,18 @@ public class Timeline {
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, "");
}
}

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.util.playback;
package de.danoeh.antennapod.core.util.gui;
import android.content.Context;
@ -7,14 +7,10 @@ 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;
@ -23,25 +19,16 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* Test class for {@link Timeline}.
* Test class for {@link ShownotesCleaner}.
*/
@RunWith(RobolectricTestRunner.class)
public class TimelineTest {
public class ShownotesCleanerTest {
private Context context;
MockedStatic<DBReader> 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
@ -50,7 +37,7 @@ public class TimelineTest {
final long time = 3600 * 1000 * 10 + 60 * 1000 * 11 + 12 * 1000;
String shownotes = "<p> Some test text with a timecode " + timeStr + " here.</p>";
Timeline t = new Timeline(context, shownotes, Integer.MAX_VALUE);
ShownotesCleaner t = new ShownotesCleaner(context, shownotes, Integer.MAX_VALUE);
String res = t.processShownotes();
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
}
@ -61,7 +48,7 @@ public class TimelineTest {
final long time = 25 * 60 * 60 * 1000;
String shownotes = "<p> Some test text with a timecode " + timeStr + " here.</p>";
Timeline t = new Timeline(context, shownotes, Integer.MAX_VALUE);
ShownotesCleaner t = new ShownotesCleaner(context, shownotes, Integer.MAX_VALUE);
String res = t.processShownotes();
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
}
@ -72,7 +59,7 @@ public class TimelineTest {
final long time = 3600 * 1000 * 10 + 60 * 1000 * 11;
String shownotes = "<p> Some test text with a timecode " + timeStr + " here.</p>";
Timeline t = new Timeline(context, shownotes, Integer.MAX_VALUE);
ShownotesCleaner t = new ShownotesCleaner(context, shownotes, Integer.MAX_VALUE);
String res = t.processShownotes();
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
}
@ -83,7 +70,7 @@ public class TimelineTest {
final long time = 10 * 60 * 1000 + 11 * 1000;
String shownotes = "<p> Some test text with a timecode " + timeStr + " here.</p>";
Timeline t = new Timeline(context, shownotes, 11 * 60 * 1000);
ShownotesCleaner t = new ShownotesCleaner(context, shownotes, 11 * 60 * 1000);
String res = t.processShownotes();
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
}
@ -94,7 +81,7 @@ public class TimelineTest {
final long time = 2 * 60 * 60 * 1000 + 11 * 60 * 1000 + 12 * 1000;
String shownotes = "<p> Some test text with a timecode " + timeStr + " here.</p>";
Timeline t = new Timeline(context, shownotes, Integer.MAX_VALUE);
ShownotesCleaner t = new ShownotesCleaner(context, shownotes, Integer.MAX_VALUE);
String res = t.processShownotes();
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
}
@ -105,7 +92,7 @@ public class TimelineTest {
final long time = 60 * 1000 + 12 * 1000;
String shownotes = "<p> Some test text with a timecode " + timeStr + " here.</p>";
Timeline t = new Timeline(context, shownotes, 2 * 60 * 1000);
ShownotesCleaner t = new ShownotesCleaner(context, shownotes, 2 * 60 * 1000);
String res = t.processShownotes();
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
}
@ -116,7 +103,7 @@ public class TimelineTest {
final int time = 2 * 60 * 60 * 1000 + 11 * 60 * 1000 + 12 * 1000;
String shownotes = "<p> Some test text with a timecode " + timeStr + " here.</p>";
Timeline t = new Timeline(context, shownotes, time);
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());
@ -128,7 +115,7 @@ public class TimelineTest {
String shownotes = "<p> Some test text with a timecode " + timeStrings[0]
+ " here. Hey look another one " + timeStrings[1] + " here!</p>";
Timeline t = new Timeline(context, shownotes, 2 * 60 * 60 * 1000);
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);
@ -142,7 +129,7 @@ public class TimelineTest {
String shownotes = "<p> Some test text with a timecode " + timeStrings[0]
+ " here. Hey look another one " + timeStrings[1] + " here!</p>";
Timeline t = new Timeline(context, shownotes, 3 * 60 * 60 * 1000);
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);
}
@ -153,7 +140,7 @@ public class TimelineTest {
final long time = 3600 * 1000 * 10 + 60 * 1000 * 11;
String shownotes = "<p> Some test text with a timecode (" + timeStr + ") here.</p>";
Timeline t = new Timeline(context, shownotes, Integer.MAX_VALUE);
ShownotesCleaner t = new ShownotesCleaner(context, shownotes, Integer.MAX_VALUE);
String res = t.processShownotes();
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
}
@ -164,7 +151,7 @@ public class TimelineTest {
final long time = 3600 * 1000 * 10 + 60 * 1000 * 11;
String shownotes = "<p> Some test text with a timecode [" + timeStr + "] here.</p>";
Timeline t = new Timeline(context, shownotes, Integer.MAX_VALUE);
ShownotesCleaner t = new ShownotesCleaner(context, shownotes, Integer.MAX_VALUE);
String res = t.processShownotes();
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
}
@ -175,7 +162,7 @@ public class TimelineTest {
final long time = 3600 * 1000 * 10 + 60 * 1000 * 11;
String shownotes = "<p> Some test text with a timecode <" + timeStr + "> here.</p>";
Timeline t = new Timeline(context, shownotes, Integer.MAX_VALUE);
ShownotesCleaner t = new ShownotesCleaner(context, shownotes, Integer.MAX_VALUE);
String res = t.processShownotes();
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
}
@ -190,7 +177,7 @@ public class TimelineTest {
}
shownotes.append("here.</p>");
Timeline t = new Timeline(context, shownotes.toString(), Integer.MAX_VALUE);
ShownotesCleaner t = new ShownotesCleaner(context, shownotes.toString(), Integer.MAX_VALUE);
String res = t.processShownotes();
checkLinkCorrect(res, new long[0], new String[0]);
}
@ -216,20 +203,34 @@ public class TimelineTest {
@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"));
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, Timeline.getTimecodeLinkTime(null));
assertEquals(-1, Timeline.getTimecodeLinkTime("http://timecode/123"));
assertEquals(123, Timeline.getTimecodeLinkTime("antennapod://timecode/123"));
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));
}
}