Merge pull request #5360 from ByteHamster/parser

Moved feed parser to its own module
This commit is contained in:
ByteHamster 2021-08-28 10:54:17 +02:00 committed by GitHub
commit e75d8db298
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 505 additions and 455 deletions

View File

@ -114,6 +114,7 @@ dependencies {
implementation project(':model')
implementation project(':net:sync:gpoddernet')
implementation project(':net:sync:model')
implementation project(':parser:feed')
implementation project(':ui:app-start-intent')
implementation project(':ui:common')

View File

@ -11,8 +11,8 @@ import java.util.ArrayList;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedFunding;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.core.syndication.namespace.PodcastIndex;
import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.parser.feed.namespace.PodcastIndex;
import de.danoeh.antennapod.core.util.DateFormatter;
/**
* Creates RSS 2.0 feeds. See FeedGenerator for more information.
@ -98,7 +98,7 @@ public class Rss2Generator implements FeedGenerator {
}
if (item.getPubDate() != null) {
xml.startTag(null, "pubDate");
xml.text(DateUtils.formatRFC822Date(item.getPubDate()));
xml.text(DateFormatter.formatRfc822Date(item.getPubDate()));
xml.endTag(null, "pubDate");
}
if ((flags & FEATURE_WRITE_GUID) != 0) {

View File

@ -46,9 +46,8 @@ import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.DownloadRequestException;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.syndication.handler.FeedHandler;
import de.danoeh.antennapod.core.syndication.handler.FeedHandlerResult;
import de.danoeh.antennapod.core.syndication.handler.UnsupportedFeedtypeException;
import de.danoeh.antennapod.parser.feed.FeedHandler;
import de.danoeh.antennapod.parser.feed.FeedHandlerResult;
import de.danoeh.antennapod.core.util.DownloadError;
import de.danoeh.antennapod.core.util.FileNameGenerator;
import de.danoeh.antennapod.core.util.IntentUtils;
@ -63,6 +62,7 @@ import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedPreferences;
import de.danoeh.antennapod.model.feed.VolumeAdaptionSetting;
import de.danoeh.antennapod.model.playback.RemoteMedia;
import de.danoeh.antennapod.parser.feed.UnsupportedFeedtypeException;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;

View File

@ -17,7 +17,7 @@ import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.model.playback.RemoteMedia;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.core.util.DateFormatter;
import de.danoeh.antennapod.model.playback.Playable;
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
import de.danoeh.antennapod.core.util.syndication.HtmlToPlainText;
@ -58,7 +58,7 @@ public class FeedItemlistDescriptionAdapter extends ArrayAdapter<FeedItem> {
}
holder.title.setText(item.getTitle());
holder.pubDate.setText(DateUtils.formatAbbrev(getContext(), item.getPubDate()));
holder.pubDate.setText(DateFormatter.formatAbbrev(getContext(), item.getPubDate()));
if (item.getDescription() != null) {
String description = HtmlToPlainText.getPlainText(item.getDescription())
.replaceAll("\n", " ")

View File

@ -7,7 +7,7 @@ import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.StatisticsItem;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.core.util.DateFormatter;
import de.danoeh.antennapod.view.PieChartView;
import java.util.Date;
@ -32,7 +32,7 @@ public class PlaybackStatisticsListAdapter extends StatisticsListAdapter {
String getHeaderCaption() {
long usageCounting = UserPreferences.getUsageCountingDateMillis();
if (usageCounting > 0) {
String date = DateUtils.formatAbbrev(context, new Date(usageCounting));
String date = DateFormatter.formatAbbrev(context, new Date(usageCounting));
return context.getString(R.string.statistics_counting_since, date);
} else {
return context.getString(R.string.total_time_listened_to_podcasts);

View File

@ -50,7 +50,7 @@ import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.util.ImageResourceUtils;
import de.danoeh.antennapod.core.glide.ApGlideSettings;
import de.danoeh.antennapod.core.util.ChapterUtils;
import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.core.util.DateFormatter;
import de.danoeh.antennapod.core.util.EmbeddedChapterImage;
import de.danoeh.antennapod.core.util.playback.PlaybackController;
import de.danoeh.antennapod.model.feed.Chapter;
@ -150,7 +150,7 @@ public class CoverFragment extends Fragment {
}
private void displayMediaInfo(@NonNull Playable media) {
String pubDateStr = DateUtils.formatAbbrev(getActivity(), media.getPubDate());
String pubDateStr = DateFormatter.formatAbbrev(getActivity(), media.getPubDate());
txtvPodcastTitle.setText(StringUtils.stripToEmpty(media.getFeedTitle())
+ "\u00A0"
+ ""

View File

@ -55,7 +55,7 @@ import de.danoeh.antennapod.core.service.download.Downloader;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.core.util.DateFormatter;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.ui.common.ThemeUtils;
import de.danoeh.antennapod.core.util.playback.PlaybackController;
@ -291,9 +291,9 @@ public class ItemFragment extends Fragment {
txtvTitle.setText(item.getTitle());
if (item.getPubDate() != null) {
String pubDateStr = DateUtils.formatAbbrev(getActivity(), item.getPubDate());
String pubDateStr = DateFormatter.formatAbbrev(getActivity(), item.getPubDate());
txtvPublished.setText(pubDateStr);
txtvPublished.setContentDescription(DateUtils.formatForAccessibility(getContext(), item.getPubDate()));
txtvPublished.setContentDescription(DateFormatter.formatForAccessibility(getContext(), item.getPubDate()));
}
RequestOptions options = new RequestOptions()

View File

@ -22,6 +22,7 @@ import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.CoverLoader;
import de.danoeh.antennapod.adapter.actionbutton.ItemActionButton;
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
import de.danoeh.antennapod.core.util.DateFormatter;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.model.playback.MediaType;
@ -31,7 +32,6 @@ import de.danoeh.antennapod.core.service.download.DownloadRequest;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.ui.common.CircularProgressBar;
@ -103,8 +103,8 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder {
placeholder.setText(item.getFeed().getTitle());
title.setText(item.getTitle());
leftPadding.setContentDescription(item.getTitle());
pubDate.setText(DateUtils.formatAbbrev(activity, item.getPubDate()));
pubDate.setContentDescription(DateUtils.formatForAccessibility(activity, item.getPubDate()));
pubDate.setText(DateFormatter.formatAbbrev(activity, item.getPubDate()));
pubDate.setContentDescription(DateFormatter.formatForAccessibility(activity, item.getPubDate()));
isNew.setVisibility(item.isNew() ? View.VISIBLE : View.GONE);
isFavorite.setVisibility(item.isTagged(FeedItem.TAG_FAVORITE) ? View.VISIBLE : View.GONE);
isInQueue.setVisibility(item.isTagged(FeedItem.TAG_QUEUE) ? View.VISIBLE : View.GONE);

View File

@ -24,6 +24,7 @@ dependencies {
implementation project(':net:ssl')
implementation project(':net:sync:gpoddernet')
implementation project(':net:sync:model')
implementation project(':parser:feed')
implementation project(':ui:app-start-intent')
implementation project(':ui:common')
implementation project(':ui:png-icons')

View File

@ -4,6 +4,7 @@ import android.content.Context;
import android.util.Log;
import android.util.Xml;
import de.danoeh.antennapod.core.util.DateFormatter;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
@ -13,7 +14,6 @@ import java.util.List;
import de.danoeh.antennapod.core.export.ExportWriter;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.core.util.DateUtils;
/** Writes OPML documents. */
public class OpmlWriter implements ExportWriter {
@ -44,7 +44,7 @@ public class OpmlWriter implements ExportWriter {
xs.text(OPML_TITLE);
xs.endTag(null, OpmlSymbols.TITLE);
xs.startTag(null, OpmlSymbols.DATE_CREATED);
xs.text(DateUtils.formatRFC822Date(new Date()));
xs.text(DateFormatter.formatRfc822Date(new Date()));
xs.endTag(null, OpmlSymbols.DATE_CREATED);
xs.endTag(null, OpmlSymbols.HEAD);

View File

@ -27,7 +27,7 @@ import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.parser.feed.util.DateUtils;
import de.danoeh.antennapod.core.util.DownloadError;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedItem;

View File

@ -24,7 +24,7 @@ import java.util.regex.Pattern;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.parser.feed.util.DateUtils;
import de.danoeh.antennapod.core.util.DownloadError;
import de.danoeh.antennapod.core.util.StorageUtils;
import de.danoeh.antennapod.core.util.URIUtil;

View File

@ -8,9 +8,9 @@ import de.danoeh.antennapod.model.feed.VolumeAdaptionSetting;
import de.danoeh.antennapod.core.service.download.DownloadRequest;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.syndication.handler.FeedHandler;
import de.danoeh.antennapod.core.syndication.handler.FeedHandlerResult;
import de.danoeh.antennapod.core.syndication.handler.UnsupportedFeedtypeException;
import de.danoeh.antennapod.parser.feed.FeedHandler;
import de.danoeh.antennapod.parser.feed.FeedHandlerResult;
import de.danoeh.antennapod.parser.feed.UnsupportedFeedtypeException;
import de.danoeh.antennapod.core.util.DownloadError;
import de.danoeh.antennapod.core.util.InvalidFeedException;
import org.xml.sax.SAXException;

View File

@ -9,7 +9,7 @@ import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DownloadRequestException;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.syndication.handler.FeedHandlerResult;
import de.danoeh.antennapod.parser.feed.FeedHandlerResult;
public class FeedSyncTask {
private static final String TAG = "FeedParserTask";

View File

@ -4,7 +4,7 @@ import android.database.Cursor;
import androidx.annotation.NonNull;
import de.danoeh.antennapod.model.feed.Chapter;
import de.danoeh.antennapod.core.feed.ID3Chapter;
import de.danoeh.antennapod.core.feed.SimpleChapter;
import de.danoeh.antennapod.parser.feed.element.SimpleChapter;
import de.danoeh.antennapod.core.feed.VorbisCommentChapter;
import de.danoeh.antennapod.core.storage.PodDBAdapter;

View File

@ -1,138 +0,0 @@
package de.danoeh.antennapod.core.syndication.handler;
import android.util.Log;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.core.syndication.namespace.NSContent;
import de.danoeh.antennapod.core.syndication.namespace.NSDublinCore;
import de.danoeh.antennapod.core.syndication.namespace.NSITunes;
import de.danoeh.antennapod.core.syndication.namespace.NSMedia;
import de.danoeh.antennapod.core.syndication.namespace.NSRSS20;
import de.danoeh.antennapod.core.syndication.namespace.NSSimpleChapters;
import de.danoeh.antennapod.core.syndication.namespace.Namespace;
import de.danoeh.antennapod.core.syndication.namespace.PodcastIndex;
import de.danoeh.antennapod.core.syndication.namespace.SyndElement;
import de.danoeh.antennapod.core.syndication.namespace.atom.NSAtom;
/** Superclass for all SAX Handlers which process Syndication formats */
class SyndHandler extends DefaultHandler {
private static final String TAG = "SyndHandler";
private static final String DEFAULT_PREFIX = "";
final HandlerState state;
public SyndHandler(Feed feed, TypeGetter.Type type) {
state = new HandlerState(feed);
if (type == TypeGetter.Type.RSS20 || type == TypeGetter.Type.RSS091) {
state.defaultNamespaces.push(new NSRSS20());
}
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
state.contentBuf = new StringBuilder();
Namespace handler = getHandlingNamespace(uri, qName);
if (handler != null) {
SyndElement element = handler.handleElementStart(localName, state,
attributes);
state.tagstack.push(element);
}
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
if (!state.tagstack.empty()) {
if (state.getTagstack().size() >= 2) {
if (state.contentBuf != null) {
state.contentBuf.append(ch, start, length);
}
}
}
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
Namespace handler = getHandlingNamespace(uri, qName);
if (handler != null) {
handler.handleElementEnd(localName, state);
state.tagstack.pop();
}
state.contentBuf = null;
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
if (state.defaultNamespaces.size() > 1 && prefix.equals(DEFAULT_PREFIX)) {
state.defaultNamespaces.pop();
}
}
@Override
public void startPrefixMapping(String prefix, String uri)
throws SAXException {
// Find the right namespace
if (!state.namespaces.containsKey(uri)) {
if (uri.equals(NSAtom.NSURI)) {
if (prefix.equals(DEFAULT_PREFIX)) {
state.defaultNamespaces.push(new NSAtom());
} else if (prefix.equals(NSAtom.NSTAG)) {
state.namespaces.put(uri, new NSAtom());
Log.d(TAG, "Recognized Atom namespace");
}
} else if (uri.equals(NSContent.NSURI)
&& prefix.equals(NSContent.NSTAG)) {
state.namespaces.put(uri, new NSContent());
Log.d(TAG, "Recognized Content namespace");
} else if (uri.equals(NSITunes.NSURI)
&& prefix.equals(NSITunes.NSTAG)) {
state.namespaces.put(uri, new NSITunes());
Log.d(TAG, "Recognized ITunes namespace");
} else if (uri.equals(NSSimpleChapters.NSURI)
&& prefix.matches(NSSimpleChapters.NSTAG)) {
state.namespaces.put(uri, new NSSimpleChapters());
Log.d(TAG, "Recognized SimpleChapters namespace");
} else if (uri.equals(NSMedia.NSURI)
&& prefix.equals(NSMedia.NSTAG)) {
state.namespaces.put(uri, new NSMedia());
Log.d(TAG, "Recognized media namespace");
} else if (uri.equals(NSDublinCore.NSURI)
&& prefix.equals(NSDublinCore.NSTAG)) {
state.namespaces.put(uri, new NSDublinCore());
Log.d(TAG, "Recognized DublinCore namespace");
} else if (uri.equals(PodcastIndex.NSURI) || uri.equals(PodcastIndex.NSURI2)
&& prefix.equals(PodcastIndex.NSTAG)) {
state.namespaces.put(uri, new PodcastIndex());
Log.d(TAG, "Recognized PodcastIndex namespace");
}
}
}
private Namespace getHandlingNamespace(String uri, String qName) {
Namespace handler = state.namespaces.get(uri);
if (handler == null && !state.defaultNamespaces.empty()
&& !qName.contains(":")) {
handler = state.defaultNamespaces.peek();
}
return handler;
}
@Override
public void endDocument() throws SAXException {
super.endDocument();
state.getFeed().setItems(state.getItems());
}
public HandlerState getState() {
return state;
}
}

View File

@ -1,120 +0,0 @@
package de.danoeh.antennapod.core.syndication.handler;
import android.util.Log;
import org.apache.commons.io.input.XmlStreamReader;
import org.jsoup.Jsoup;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;
import de.danoeh.antennapod.model.feed.Feed;
/** Gets the type of a specific feed by reading the root element. */
public class TypeGetter {
private static final String TAG = "TypeGetter";
public enum Type {
RSS20, RSS091, ATOM, INVALID
}
private static final String ATOM_ROOT = "feed";
private static final String RSS_ROOT = "rss";
public Type getType(Feed feed) throws UnsupportedFeedtypeException {
XmlPullParserFactory factory;
if (feed.getFile_url() != null) {
Reader reader = null;
try {
factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
XmlPullParser xpp = factory.newPullParser();
reader = createReader(feed);
xpp.setInput(reader);
int eventType = xpp.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
String tag = xpp.getName();
switch (tag) {
case ATOM_ROOT:
feed.setType(Feed.TYPE_ATOM1);
Log.d(TAG, "Recognized type Atom");
String strLang = xpp.getAttributeValue("http://www.w3.org/XML/1998/namespace", "lang");
if (strLang != null) {
feed.setLanguage(strLang);
}
return Type.ATOM;
case RSS_ROOT:
String strVersion = xpp.getAttributeValue(null, "version");
if (strVersion == null) {
feed.setType(Feed.TYPE_RSS2);
Log.d(TAG, "Assuming type RSS 2.0");
return Type.RSS20;
} else if (strVersion.equals("2.0")) {
feed.setType(Feed.TYPE_RSS2);
Log.d(TAG, "Recognized type RSS 2.0");
return Type.RSS20;
} else if (strVersion.equals("0.91") || strVersion.equals("0.92")) {
Log.d(TAG, "Recognized type RSS 0.91/0.92");
return Type.RSS091;
}
throw new UnsupportedFeedtypeException("Unsupported rss version");
default:
Log.d(TAG, "Type is invalid");
throw new UnsupportedFeedtypeException(Type.INVALID, tag);
}
} else {
eventType = xpp.next();
}
}
} catch (XmlPullParserException e) {
e.printStackTrace();
// XML document might actually be a HTML document -> try to parse as HTML
String rootElement = null;
try {
if (Jsoup.parse(new File(feed.getFile_url()), null) != null) {
rootElement = "html";
}
} catch (IOException e1) {
e1.printStackTrace();
}
throw new UnsupportedFeedtypeException(Type.INVALID, rootElement);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Log.d(TAG, "Type is invalid");
throw new UnsupportedFeedtypeException(Type.INVALID);
}
private Reader createReader(Feed feed) {
Reader reader;
try {
reader = new XmlStreamReader(new File(feed.getFile_url()));
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
return reader;
}
}

View File

@ -1,21 +0,0 @@
package de.danoeh.antennapod.core.syndication.namespace;
import org.xml.sax.Attributes;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
public abstract class Namespace {
public static final String NSTAG = null;
public static final String NSURI = null;
/** Called by a Feedhandler when in startElement and it detects a namespace element
* @return The SyndElement to push onto the stack
* */
public abstract SyndElement handleElementStart(String localName, HandlerState state, Attributes attributes);
/** Called by a Feedhandler when in endElement and it detects a namespace element
* */
public abstract void handleElementEnd(String localName, HandlerState state);
}

View File

@ -1,22 +0,0 @@
package de.danoeh.antennapod.core.syndication.namespace;
/** Defines a XML Element that is pushed on the tagstack */
public class SyndElement {
private final String name;
private final Namespace namespace;
public SyndElement(String name, Namespace namespace) {
this.name = name;
this.namespace = namespace;
}
public Namespace getNamespace() {
return namespace;
}
public String getName() {
return name;
}
}

View File

@ -0,0 +1,46 @@
package de.danoeh.antennapod.core.util;
import android.content.Context;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
/**
* Formats dates.
*/
public class DateFormatter {
private DateFormatter() {
}
public static String formatRfc822Date(Date date) {
SimpleDateFormat format = new SimpleDateFormat("dd MMM yy HH:mm:ss Z", Locale.US);
return format.format(date);
}
public static String formatAbbrev(final Context context, final Date date) {
if (date == null) {
return "";
}
GregorianCalendar now = new GregorianCalendar();
GregorianCalendar cal = new GregorianCalendar();
cal.setTime(date);
boolean withinLastYear = now.get(Calendar.YEAR) == cal.get(Calendar.YEAR);
int format = android.text.format.DateUtils.FORMAT_ABBREV_ALL;
if (withinLastYear) {
format |= android.text.format.DateUtils.FORMAT_NO_YEAR;
}
return android.text.format.DateUtils.formatDateTime(context, date.getTime(), format);
}
public static String formatForAccessibility(final Context context, final Date date) {
if (date == null) {
return "";
}
return DateFormat.getDateInstance(DateFormat.LONG).format(date);
}
}

View File

@ -9,7 +9,7 @@ import de.danoeh.antennapod.model.feed.Chapter;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.SimpleChapter;
import de.danoeh.antennapod.parser.feed.element.SimpleChapter;
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
import static org.junit.Assert.assertTrue;

3
parser/README.md Normal file
View File

@ -0,0 +1,3 @@
# :parser
This folder contains modules that parse data, for example XML or media files.

3
parser/feed/README.md Normal file
View File

@ -0,0 +1,3 @@
# :parser:feed
This module provides the XML feed parser.

23
parser/feed/build.gradle Normal file
View File

@ -0,0 +1,23 @@
apply plugin: "com.android.library"
apply from: "../../common.gradle"
android {
lintOptions {
disable "TrustAllX509TrustManager"
}
}
dependencies {
implementation project(':model')
annotationProcessor "androidx.annotation:annotation:$annotationVersion"
implementation "androidx.core:core:$appcompatVersion"
implementation "org.apache.commons:commons-lang3:$commonslangVersion"
implementation "commons-io:commons-io:$commonsioVersion"
implementation "org.jsoup:jsoup:$jsoupVersion"
testImplementation 'junit:junit:4.13'
testImplementation 'org.robolectric:robolectric:4.5-alpha-1'
}

View File

@ -0,0 +1 @@
<manifest package="de.danoeh.antennapod.parser.feed" />

View File

@ -1,8 +1,9 @@
package de.danoeh.antennapod.core.syndication.handler;
package de.danoeh.antennapod.parser.feed;
import android.text.TextUtils;
import android.util.Log;
import de.danoeh.antennapod.parser.feed.util.TypeGetter;
import org.apache.commons.io.input.XmlStreamReader;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.syndication.handler;
package de.danoeh.antennapod.parser.feed;
import java.util.Map;

View File

@ -1,16 +1,15 @@
package de.danoeh.antennapod.core.syndication.handler;
import androidx.collection.ArrayMap;
package de.danoeh.antennapod.parser.feed;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedFunding;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.core.syndication.namespace.Namespace;
import de.danoeh.antennapod.core.syndication.namespace.SyndElement;
import de.danoeh.antennapod.parser.feed.namespace.Namespace;
import de.danoeh.antennapod.parser.feed.element.SyndElement;
/**
* Contains all relevant information to describe the current state of a
@ -21,12 +20,12 @@ public class HandlerState {
/**
* Feed that the Handler is currently processing.
*/
Feed feed;
public Feed feed;
/**
* Contains links to related feeds, e.g. feeds with enclosures in other formats. The key of the map is the
* URL of the feed, the value is the title
*/
final Map<String, String> alternateUrls;
public final Map<String, String> alternateUrls;
private final ArrayList<FeedItem> items;
private FeedItem currentItem;
private FeedFunding currentFunding;
@ -48,12 +47,12 @@ public class HandlerState {
public HandlerState(Feed feed) {
this.feed = feed;
alternateUrls = new ArrayMap<>();
alternateUrls = new HashMap<>();
items = new ArrayList<>();
tagstack = new Stack<>();
namespaces = new ArrayMap<>();
namespaces = new HashMap<>();
defaultNamespaces = new Stack<>();
tempObjects = new ArrayMap<>();
tempObjects = new HashMap<>();
}
public Feed getFeed() {

View File

@ -0,0 +1,139 @@
package de.danoeh.antennapod.parser.feed;
import android.util.Log;
import de.danoeh.antennapod.parser.feed.util.TypeGetter;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.parser.feed.namespace.Content;
import de.danoeh.antennapod.parser.feed.namespace.DublinCore;
import de.danoeh.antennapod.parser.feed.namespace.Itunes;
import de.danoeh.antennapod.parser.feed.namespace.Media;
import de.danoeh.antennapod.parser.feed.namespace.Rss20;
import de.danoeh.antennapod.parser.feed.namespace.SimpleChapters;
import de.danoeh.antennapod.parser.feed.namespace.Namespace;
import de.danoeh.antennapod.parser.feed.namespace.PodcastIndex;
import de.danoeh.antennapod.parser.feed.element.SyndElement;
import de.danoeh.antennapod.parser.feed.namespace.Atom;
/** Superclass for all SAX Handlers which process Syndication formats */
public class SyndHandler extends DefaultHandler {
private static final String TAG = "SyndHandler";
private static final String DEFAULT_PREFIX = "";
public final HandlerState state;
public SyndHandler(Feed feed, TypeGetter.Type type) {
state = new HandlerState(feed);
if (type == TypeGetter.Type.RSS20 || type == TypeGetter.Type.RSS091) {
state.defaultNamespaces.push(new Rss20());
}
}
@Override
public void startElement(String uri, String localName, String qualifiedName,
Attributes attributes) throws SAXException {
state.contentBuf = new StringBuilder();
Namespace handler = getHandlingNamespace(uri, qualifiedName);
if (handler != null) {
SyndElement element = handler.handleElementStart(localName, state,
attributes);
state.tagstack.push(element);
}
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
if (!state.tagstack.empty()) {
if (state.getTagstack().size() >= 2) {
if (state.contentBuf != null) {
state.contentBuf.append(ch, start, length);
}
}
}
}
@Override
public void endElement(String uri, String localName, String qualifiedName)
throws SAXException {
Namespace handler = getHandlingNamespace(uri, qualifiedName);
if (handler != null) {
handler.handleElementEnd(localName, state);
state.tagstack.pop();
}
state.contentBuf = null;
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
if (state.defaultNamespaces.size() > 1 && prefix.equals(DEFAULT_PREFIX)) {
state.defaultNamespaces.pop();
}
}
@Override
public void startPrefixMapping(String prefix, String uri)
throws SAXException {
// Find the right namespace
if (!state.namespaces.containsKey(uri)) {
if (uri.equals(Atom.NSURI)) {
if (prefix.equals(DEFAULT_PREFIX)) {
state.defaultNamespaces.push(new Atom());
} else if (prefix.equals(Atom.NSTAG)) {
state.namespaces.put(uri, new Atom());
Log.d(TAG, "Recognized Atom namespace");
}
} else if (uri.equals(Content.NSURI)
&& prefix.equals(Content.NSTAG)) {
state.namespaces.put(uri, new Content());
Log.d(TAG, "Recognized Content namespace");
} else if (uri.equals(Itunes.NSURI)
&& prefix.equals(Itunes.NSTAG)) {
state.namespaces.put(uri, new Itunes());
Log.d(TAG, "Recognized ITunes namespace");
} else if (uri.equals(SimpleChapters.NSURI)
&& prefix.matches(SimpleChapters.NSTAG)) {
state.namespaces.put(uri, new SimpleChapters());
Log.d(TAG, "Recognized SimpleChapters namespace");
} else if (uri.equals(Media.NSURI)
&& prefix.equals(Media.NSTAG)) {
state.namespaces.put(uri, new Media());
Log.d(TAG, "Recognized media namespace");
} else if (uri.equals(DublinCore.NSURI)
&& prefix.equals(DublinCore.NSTAG)) {
state.namespaces.put(uri, new DublinCore());
Log.d(TAG, "Recognized DublinCore namespace");
} else if (uri.equals(PodcastIndex.NSURI) || uri.equals(PodcastIndex.NSURI2)
&& prefix.equals(PodcastIndex.NSTAG)) {
state.namespaces.put(uri, new PodcastIndex());
Log.d(TAG, "Recognized PodcastIndex namespace");
}
}
}
private Namespace getHandlingNamespace(String uri, String qualifiedName) {
Namespace handler = state.namespaces.get(uri);
if (handler == null && !state.defaultNamespaces.empty()
&& !qualifiedName.contains(":")) {
handler = state.defaultNamespaces.peek();
}
return handler;
}
@Override
public void endDocument() throws SAXException {
super.endDocument();
state.getFeed().setItems(state.getItems());
}
public HandlerState getState() {
return state;
}
}

View File

@ -1,6 +1,7 @@
package de.danoeh.antennapod.core.syndication.handler;
package de.danoeh.antennapod.parser.feed;
import de.danoeh.antennapod.core.syndication.handler.TypeGetter.Type;
import de.danoeh.antennapod.parser.feed.util.TypeGetter;
import de.danoeh.antennapod.parser.feed.util.TypeGetter.Type;
public class UnsupportedFeedtypeException extends Exception {
private static final long serialVersionUID = 9105878964928170669L;

View File

@ -1,13 +1,11 @@
package de.danoeh.antennapod.core.syndication.namespace.atom;
package de.danoeh.antennapod.parser.feed.element;
import androidx.core.text.HtmlCompat;
import de.danoeh.antennapod.core.syndication.namespace.Namespace;
import de.danoeh.antennapod.core.syndication.namespace.SyndElement;
import de.danoeh.antennapod.parser.feed.namespace.Namespace;
/** Represents Atom Element which contains text (content, title, summary). */
public class AtomText extends SyndElement {
public static final String TYPE_TEXT = "text";
public static final String TYPE_HTML = "html";
private static final String TYPE_XHTML = "xhtml";

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.feed;
package de.danoeh.antennapod.parser.feed.element;
import de.danoeh.antennapod.model.feed.Chapter;

View File

@ -0,0 +1,22 @@
package de.danoeh.antennapod.parser.feed.element;
import de.danoeh.antennapod.parser.feed.namespace.Namespace;
/** Defines a XML Element that is pushed on the tagstack */
public class SyndElement {
private final String name;
private final Namespace namespace;
public SyndElement(String name, Namespace namespace) {
this.name = name;
this.namespace = namespace;
}
public Namespace getNamespace() {
return namespace;
}
public String getName() {
return name;
}
}

View File

@ -1,23 +1,21 @@
package de.danoeh.antennapod.core.syndication.namespace.atom;
package de.danoeh.antennapod.parser.feed.namespace;
import android.text.TextUtils;
import android.util.Log;
import de.danoeh.antennapod.model.feed.FeedFunding;
import de.danoeh.antennapod.core.syndication.util.SyndStringUtils;
import de.danoeh.antennapod.parser.feed.HandlerState;
import de.danoeh.antennapod.parser.feed.element.AtomText;
import de.danoeh.antennapod.parser.feed.util.DateUtils;
import de.danoeh.antennapod.parser.feed.util.SyndStringUtils;
import org.xml.sax.Attributes;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
import de.danoeh.antennapod.core.syndication.namespace.NSITunes;
import de.danoeh.antennapod.core.syndication.namespace.NSRSS20;
import de.danoeh.antennapod.core.syndication.namespace.Namespace;
import de.danoeh.antennapod.core.syndication.namespace.SyndElement;
import de.danoeh.antennapod.core.syndication.util.SyndTypeUtils;
import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.parser.feed.element.SyndElement;
import de.danoeh.antennapod.parser.feed.util.SyndTypeUtils;
public class NSAtom extends Namespace {
public class Atom extends Namespace {
private static final String TAG = "NSAtom";
public static final String NSTAG = "atom";
public static final String NSURI = "http://www.w3.org/2005/Atom";
@ -63,8 +61,8 @@ public class NSAtom extends Namespace {
private static final String isText = TITLE + "|" + CONTENT + "|"
+ SUBTITLE + "|" + SUMMARY;
private static final String isFeed = FEED + "|" + NSRSS20.CHANNEL;
private static final String isFeedItem = ENTRY + "|" + NSRSS20.ITEM;
private static final String isFeed = FEED + "|" + Rss20.CHANNEL;
private static final String isFeedItem = ENTRY + "|" + Rss20.ITEM;
@Override
public SyndElement handleElementStart(String localName, HandlerState state,
@ -152,13 +150,13 @@ public class NSAtom extends Namespace {
public void handleElementEnd(String localName, HandlerState state) {
if (ENTRY.equals(localName)) {
if (state.getCurrentItem() != null &&
state.getTempObjects().containsKey(NSITunes.DURATION)) {
state.getTempObjects().containsKey(Itunes.DURATION)) {
FeedItem currentItem = state.getCurrentItem();
if (currentItem.hasMedia()) {
Integer duration = (Integer) state.getTempObjects().get(NSITunes.DURATION);
Integer duration = (Integer) state.getTempObjects().get(Itunes.DURATION);
currentItem.getMedia().setDuration(duration);
}
state.getTempObjects().remove(NSITunes.DURATION);
state.getTempObjects().remove(Itunes.DURATION);
}
state.setCurrentItem(null);
}

View File

@ -1,10 +1,10 @@
package de.danoeh.antennapod.core.syndication.namespace;
package de.danoeh.antennapod.parser.feed.namespace;
import de.danoeh.antennapod.parser.feed.HandlerState;
import de.danoeh.antennapod.parser.feed.element.SyndElement;
import org.xml.sax.Attributes;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
public class NSContent extends Namespace {
public class Content extends Namespace {
public static final String NSTAG = "content";
public static final String NSURI = "http://purl.org/rss/1.0/modules/content/";
@ -21,5 +21,4 @@ public class NSContent extends Namespace {
state.getCurrentItem().setDescriptionIfLonger(state.getContentBuf().toString());
}
}
}

View File

@ -1,12 +1,13 @@
package de.danoeh.antennapod.core.syndication.namespace;
package de.danoeh.antennapod.parser.feed.namespace;
import de.danoeh.antennapod.parser.feed.HandlerState;
import de.danoeh.antennapod.parser.feed.element.SyndElement;
import de.danoeh.antennapod.parser.feed.util.DateUtils;
import org.xml.sax.Attributes;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
import de.danoeh.antennapod.core.util.DateUtils;
public class NSDublinCore extends Namespace {
public class DublinCore extends Namespace {
private static final String TAG = "NSDublinCore";
public static final String NSTAG = "dc";
public static final String NSURI = "http://purl.org/dc/elements/1.1/";

View File

@ -1,16 +1,17 @@
package de.danoeh.antennapod.core.syndication.namespace;
package de.danoeh.antennapod.parser.feed.namespace;
import android.text.TextUtils;
import android.util.Log;
import androidx.core.text.HtmlCompat;
import de.danoeh.antennapod.parser.feed.HandlerState;
import de.danoeh.antennapod.parser.feed.element.SyndElement;
import de.danoeh.antennapod.parser.feed.util.DurationParser;
import org.xml.sax.Attributes;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
import de.danoeh.antennapod.core.syndication.parsers.DurationParser;
public class NSITunes extends Namespace {
public class Itunes extends Namespace {
public static final String NSTAG = "itunes";
public static final String NSURI = "http://www.itunes.com/dtds/podcast-1.0.dtd";
@ -106,7 +107,7 @@ public class NSITunes extends Namespace {
if (state.getCurrentItem() != null) {
state.getCurrentItem().setDescriptionIfLonger(summary);
} else if (NSRSS20.CHANNEL.equals(secondElementName) && state.getFeed() != null) {
} else if (Rss20.CHANNEL.equals(secondElementName) && state.getFeed() != null) {
state.getFeed().setDescription(summary);
}
}

View File

@ -1,19 +1,20 @@
package de.danoeh.antennapod.core.syndication.namespace;
package de.danoeh.antennapod.parser.feed.namespace;
import android.text.TextUtils;
import android.util.Log;
import de.danoeh.antennapod.parser.feed.HandlerState;
import de.danoeh.antennapod.parser.feed.element.SyndElement;
import org.xml.sax.Attributes;
import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
import de.danoeh.antennapod.core.syndication.namespace.atom.AtomText;
import de.danoeh.antennapod.core.syndication.util.SyndTypeUtils;
import de.danoeh.antennapod.parser.feed.element.AtomText;
import de.danoeh.antennapod.parser.feed.util.SyndTypeUtils;
/** Processes tags from the http://search.yahoo.com/mrss/ namespace. */
public class NSMedia extends Namespace {
public class Media extends Namespace {
private static final String TAG = "NSMedia";
public static final String NSTAG = "media";

View File

@ -0,0 +1,19 @@
package de.danoeh.antennapod.parser.feed.namespace;
import de.danoeh.antennapod.parser.feed.HandlerState;
import de.danoeh.antennapod.parser.feed.element.SyndElement;
import org.xml.sax.Attributes;
public abstract class Namespace {
public static final String NSTAG = null;
public static final String NSURI = null;
/** Called by a Feedhandler when in startElement and it detects a namespace element
* @return The SyndElement to push onto the stack
* */
public abstract SyndElement handleElementStart(String localName, HandlerState state, Attributes attributes);
/** Called by a Feedhandler when in endElement and it detects a namespace element
* */
public abstract void handleElementEnd(String localName, HandlerState state);
}

View File

@ -1,9 +1,10 @@
package de.danoeh.antennapod.core.syndication.namespace;
package de.danoeh.antennapod.parser.feed.namespace;
import de.danoeh.antennapod.parser.feed.HandlerState;
import de.danoeh.antennapod.parser.feed.element.SyndElement;
import org.jsoup.helper.StringUtil;
import org.xml.sax.Attributes;
import de.danoeh.antennapod.model.feed.FeedFunding;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
public class PodcastIndex extends Namespace {

View File

@ -1,21 +1,24 @@
package de.danoeh.antennapod.core.syndication.namespace;
package de.danoeh.antennapod.parser.feed.namespace;
import android.text.TextUtils;
import android.util.Log;
import de.danoeh.antennapod.core.syndication.util.SyndStringUtils;
import de.danoeh.antennapod.parser.feed.HandlerState;
import de.danoeh.antennapod.parser.feed.element.SyndElement;
import de.danoeh.antennapod.parser.feed.util.DateUtils;
import de.danoeh.antennapod.parser.feed.util.SyndStringUtils;
import org.xml.sax.Attributes;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
import de.danoeh.antennapod.core.syndication.util.SyndTypeUtils;
import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.parser.feed.util.SyndTypeUtils;
import java.util.Locale;
/**
* SAX-Parser for reading RSS-Feeds.
*/
public class NSRSS20 extends Namespace {
public class Rss20 extends Namespace {
private static final String TAG = "NSRSS20";
@ -85,12 +88,12 @@ public class NSRSS20 extends Namespace {
currentItem.setTitle(currentItem.getDescription());
}
if (state.getTempObjects().containsKey(NSITunes.DURATION)) {
if (state.getTempObjects().containsKey(Itunes.DURATION)) {
if (currentItem.hasMedia()) {
Integer duration = (Integer) state.getTempObjects().get(NSITunes.DURATION);
Integer duration = (Integer) state.getTempObjects().get(Itunes.DURATION);
currentItem.getMedia().setDuration(duration);
}
state.getTempObjects().remove(NSITunes.DURATION);
state.getTempObjects().remove(Itunes.DURATION);
}
}
state.setCurrentItem(null);
@ -137,7 +140,7 @@ public class NSRSS20 extends Namespace {
state.getCurrentItem().setDescriptionIfLonger(content);
}
} else if (LANGUAGE.equals(localName) && state.getFeed() != null) {
state.getFeed().setLanguage(content.toLowerCase());
state.getFeed().setLanguage(content.toLowerCase(Locale.US));
}
}
}

View File

@ -1,17 +1,18 @@
package de.danoeh.antennapod.core.syndication.namespace;
package de.danoeh.antennapod.parser.feed.namespace;
import android.util.Log;
import de.danoeh.antennapod.parser.feed.HandlerState;
import de.danoeh.antennapod.parser.feed.element.SimpleChapter;
import de.danoeh.antennapod.parser.feed.element.SyndElement;
import de.danoeh.antennapod.parser.feed.util.DateUtils;
import org.xml.sax.Attributes;
import java.util.ArrayList;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.core.feed.SimpleChapter;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
import de.danoeh.antennapod.core.util.DateUtils;
public class NSSimpleChapters extends Namespace {
public class SimpleChapters extends Namespace {
private static final String TAG = "NSSimpleChapters";
public static final String NSTAG = "psc|sc";

View File

@ -1,17 +1,13 @@
package de.danoeh.antennapod.core.util;
package de.danoeh.antennapod.parser.feed.util;
import android.content.Context;
import android.util.Log;
import androidx.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
import java.text.DateFormat;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
@ -20,7 +16,9 @@ import java.util.TimeZone;
*/
public class DateUtils {
private DateUtils(){}
private DateUtils() {
}
private static final String TAG = "DateUtils";
private static final TimeZone defaultTimezone = TimeZone.getTimeZone("GMT");
@ -58,7 +56,8 @@ public class DateUtils {
// less than 4 decimal places: pad to have a consistent format for the parser
} else if (current - start < 4) {
if (current < date.length() - 1) {
date = date.substring(0, current) + StringUtils.repeat("0", 4 - (current - start)) + date.substring(current);
date = date.substring(0, current) + StringUtils.repeat("0", 4 - (current - start))
+ date.substring(current);
} else {
date = date.substring(0, current) + StringUtils.repeat("0", 4 - (current - start));
}
@ -161,42 +160,4 @@ public class DateUtils {
}
return result;
}
public static String formatRFC822Date(Date date) {
SimpleDateFormat format = new SimpleDateFormat("dd MMM yy HH:mm:ss Z", Locale.US);
return format.format(date);
}
public static String formatRFC3339Local(Date date) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US);
return format.format(date);
}
public static String formatRFC3339UTC(Date date) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
format.setTimeZone(defaultTimezone);
return format.format(date);
}
public static String formatAbbrev(final Context context, final Date date) {
if (date == null) {
return "";
}
GregorianCalendar now = new GregorianCalendar();
GregorianCalendar cal = new GregorianCalendar();
cal.setTime(date);
boolean withinLastYear = now.get(Calendar.YEAR) == cal.get(Calendar.YEAR);
int format = android.text.format.DateUtils.FORMAT_ABBREV_ALL;
if (withinLastYear) {
format |= android.text.format.DateUtils.FORMAT_NO_YEAR;
}
return android.text.format.DateUtils.formatDateTime(context, date.getTime(), format);
}
public static String formatForAccessibility(final Context context, final Date date) {
if (date == null) {
return "";
}
return DateFormat.getDateInstance(DateFormat.LONG).format(date);
}
}

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.syndication.parsers;
package de.danoeh.antennapod.parser.feed.util;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MINUTES;

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.syndication.util;
package de.danoeh.antennapod.parser.feed.util;
public class SyndStringUtils {
private SyndStringUtils() {

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.syndication.util;
package de.danoeh.antennapod.parser.feed.util;
import android.webkit.MimeTypeMap;
import org.apache.commons.io.FilenameUtils;

View File

@ -0,0 +1,121 @@
package de.danoeh.antennapod.parser.feed.util;
import android.util.Log;
import de.danoeh.antennapod.parser.feed.UnsupportedFeedtypeException;
import org.apache.commons.io.input.XmlStreamReader;
import org.jsoup.Jsoup;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;
import de.danoeh.antennapod.model.feed.Feed;
/** Gets the type of a specific feed by reading the root element. */
public class TypeGetter {
private static final String TAG = "TypeGetter";
public enum Type {
RSS20, RSS091, ATOM, INVALID
}
private static final String ATOM_ROOT = "feed";
private static final String RSS_ROOT = "rss";
public Type getType(Feed feed) throws UnsupportedFeedtypeException {
XmlPullParserFactory factory;
if (feed.getFile_url() != null) {
Reader reader = null;
try {
factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
XmlPullParser xpp = factory.newPullParser();
reader = createReader(feed);
xpp.setInput(reader);
int eventType = xpp.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
String tag = xpp.getName();
switch (tag) {
case ATOM_ROOT:
feed.setType(Feed.TYPE_ATOM1);
Log.d(TAG, "Recognized type Atom");
String strLang = xpp.getAttributeValue("http://www.w3.org/XML/1998/namespace", "lang");
if (strLang != null) {
feed.setLanguage(strLang);
}
return Type.ATOM;
case RSS_ROOT:
String strVersion = xpp.getAttributeValue(null, "version");
if (strVersion == null) {
feed.setType(Feed.TYPE_RSS2);
Log.d(TAG, "Assuming type RSS 2.0");
return Type.RSS20;
} else if (strVersion.equals("2.0")) {
feed.setType(Feed.TYPE_RSS2);
Log.d(TAG, "Recognized type RSS 2.0");
return Type.RSS20;
} else if (strVersion.equals("0.91") || strVersion.equals("0.92")) {
Log.d(TAG, "Recognized type RSS 0.91/0.92");
return Type.RSS091;
}
throw new UnsupportedFeedtypeException("Unsupported rss version");
default:
Log.d(TAG, "Type is invalid");
throw new UnsupportedFeedtypeException(Type.INVALID, tag);
}
} else {
eventType = xpp.next();
}
}
} catch (XmlPullParserException e) {
e.printStackTrace();
// XML document might actually be a HTML document -> try to parse as HTML
String rootElement = null;
try {
if (Jsoup.parse(new File(feed.getFile_url()), null) != null) {
rootElement = "html";
}
} catch (IOException e1) {
e1.printStackTrace();
}
throw new UnsupportedFeedtypeException(Type.INVALID, rootElement);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Log.d(TAG, "Type is invalid");
throw new UnsupportedFeedtypeException(Type.INVALID);
}
private Reader createReader(Feed feed) {
Reader reader;
try {
reader = new XmlStreamReader(new File(feed.getFile_url()));
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
return reader;
}
}

View File

@ -1,5 +1,7 @@
package de.danoeh.antennapod.core.syndication.namespace.atom;
package de.danoeh.antennapod.parser.feed.element.element;
import de.danoeh.antennapod.parser.feed.element.AtomText;
import de.danoeh.antennapod.parser.feed.namespace.Atom;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@ -27,7 +29,7 @@ public class AtomTextTest {
@Test
public void testProcessingHtml() {
for (String[] pair : TEST_DATA) {
final AtomText atomText = new AtomText("", new NSAtom(), AtomText.TYPE_HTML);
final AtomText atomText = new AtomText("", new Atom(), AtomText.TYPE_HTML);
atomText.setContent(pair[0]);
assertEquals(pair[1], atomText.getProcessedContent());
}

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.syndication.handler;
package de.danoeh.antennapod.parser.feed.element.namespace;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -12,9 +12,9 @@ import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
/**
* Tests for Atom feeds in FeedHandler.

View File

@ -1,10 +1,11 @@
package de.danoeh.antennapod.core.syndication.handler;
package de.danoeh.antennapod.parser.feed.element.namespace;
import androidx.annotation.NonNull;
import java.io.File;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.parser.feed.FeedHandler;
/**
* Tests for FeedHandler.

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.syndication.handler;
package de.danoeh.antennapod.parser.feed.element.namespace;
import android.text.TextUtils;
import org.junit.Test;

View File

@ -1,5 +1,6 @@
package de.danoeh.antennapod.core.util;
package de.danoeh.antennapod.parser.feed.element.util;
import de.danoeh.antennapod.parser.feed.util.DateUtils;
import org.junit.Test;
import java.util.Calendar;

View File

@ -1,5 +1,6 @@
package de.danoeh.antennapod.core.syndication.parsers;
package de.danoeh.antennapod.parser.feed.element.util;
import de.danoeh.antennapod.parser.feed.util.DurationParser;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

View File

@ -6,6 +6,8 @@ include ':net:ssl'
include ':net:sync:gpoddernet'
include ':net:sync:model'
include ':parser:feed'
include ':ui:app-start-intent'
include ':ui:common'
include ':ui:png-icons'