Moved feed parser to its own module
This commit is contained in:
parent
85c8a419ac
commit
24389d42e8
|
@ -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')
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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", " ")
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
+ "・"
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# :parser
|
||||
|
||||
This folder contains modules that parse data, for example XML or media files.
|
|
@ -0,0 +1,3 @@
|
|||
# :parser:feed
|
||||
|
||||
This module provides the XML feed parser.
|
|
@ -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'
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<manifest package="de.danoeh.antennapod.parser.feed" />
|
|
@ -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;
|
|
@ -1,4 +1,4 @@
|
|||
package de.danoeh.antennapod.core.syndication.handler;
|
||||
package de.danoeh.antennapod.parser.feed;
|
||||
|
||||
import java.util.Map;
|
||||
|
|
@ -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() {
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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";
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package de.danoeh.antennapod.core.feed;
|
||||
package de.danoeh.antennapod.parser.feed.element;
|
||||
|
||||
import de.danoeh.antennapod.model.feed.Chapter;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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/";
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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";
|
|
@ -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);
|
||||
}
|
|
@ -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 {
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -1,4 +1,4 @@
|
|||
package de.danoeh.antennapod.core.syndication.util;
|
||||
package de.danoeh.antennapod.parser.feed.util;
|
||||
|
||||
public class SyndStringUtils {
|
||||
private SyndStringUtils() {
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
|
@ -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.
|
|
@ -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.
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue