summaryrefslogtreecommitdiff
path: root/src/de/danoeh/antennapod/syndication
diff options
context:
space:
mode:
authordaniel oeh <daniel.oeh@gmail.com>2012-07-13 12:23:47 +0200
committerdaniel oeh <daniel.oeh@gmail.com>2012-07-13 12:23:47 +0200
commitba2d2afbbc6cbb79fc75493703425b5d6d040530 (patch)
treee731a1209160e8224679cb238c0a964c3e757590 /src/de/danoeh/antennapod/syndication
parent1ae00a0f2531fdb05a44877dda88ee2300e3ffec (diff)
downloadAntennaPod-ba2d2afbbc6cbb79fc75493703425b5d6d040530.zip
Renamed package and application
Diffstat (limited to 'src/de/danoeh/antennapod/syndication')
-rw-r--r--src/de/danoeh/antennapod/syndication/handler/FeedHandler.java29
-rw-r--r--src/de/danoeh/antennapod/syndication/handler/HandlerState.java69
-rw-r--r--src/de/danoeh/antennapod/syndication/handler/SyndHandler.java112
-rw-r--r--src/de/danoeh/antennapod/syndication/handler/TypeGetter.java76
-rw-r--r--src/de/danoeh/antennapod/syndication/handler/UnsupportedFeedtypeException.java29
-rw-r--r--src/de/danoeh/antennapod/syndication/namespace/Namespace.java23
-rw-r--r--src/de/danoeh/antennapod/syndication/namespace/SyndElement.java22
-rw-r--r--src/de/danoeh/antennapod/syndication/namespace/atom/AtomText.java47
-rw-r--r--src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java146
-rw-r--r--src/de/danoeh/antennapod/syndication/namespace/content/NSContent.java31
-rw-r--r--src/de/danoeh/antennapod/syndication/namespace/itunes/NSITunes.java42
-rw-r--r--src/de/danoeh/antennapod/syndication/namespace/rss20/NSRSS20.java115
-rw-r--r--src/de/danoeh/antennapod/syndication/namespace/simplechapters/NSSimpleChapters.java43
-rw-r--r--src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java102
14 files changed, 886 insertions, 0 deletions
diff --git a/src/de/danoeh/antennapod/syndication/handler/FeedHandler.java b/src/de/danoeh/antennapod/syndication/handler/FeedHandler.java
new file mode 100644
index 000000000..dfcfcf98d
--- /dev/null
+++ b/src/de/danoeh/antennapod/syndication/handler/FeedHandler.java
@@ -0,0 +1,29 @@
+package de.danoeh.antennapod.syndication.handler;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.xml.sax.SAXException;
+
+import de.danoeh.antennapod.feed.Feed;
+
+public class FeedHandler {
+
+ public Feed parseFeed(Feed feed) throws SAXException, IOException,
+ ParserConfigurationException, UnsupportedFeedtypeException {
+ TypeGetter tg = new TypeGetter();
+ TypeGetter.Type type = tg.getType(feed);
+ SyndHandler handler = new SyndHandler(feed, type);
+
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ factory.setNamespaceAware(true);
+ SAXParser saxParser = factory.newSAXParser();
+ saxParser.parse(new File(feed.getFile_url()), handler);
+
+ return handler.state.feed;
+ }
+}
diff --git a/src/de/danoeh/antennapod/syndication/handler/HandlerState.java b/src/de/danoeh/antennapod/syndication/handler/HandlerState.java
new file mode 100644
index 000000000..1d81d0fb1
--- /dev/null
+++ b/src/de/danoeh/antennapod/syndication/handler/HandlerState.java
@@ -0,0 +1,69 @@
+package de.danoeh.antennapod.syndication.handler;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Stack;
+
+import de.danoeh.antennapod.feed.Feed;
+import de.danoeh.antennapod.feed.FeedItem;
+import de.danoeh.antennapod.syndication.namespace.Namespace;
+import de.danoeh.antennapod.syndication.namespace.SyndElement;
+
+/** Contains all relevant information to describe the current state of a SyndHandler.*/
+public class HandlerState {
+
+ /** Feed that the Handler is currently processing. */
+ protected Feed feed;
+ protected FeedItem currentItem;
+ protected Stack<SyndElement> tagstack;
+ /** Namespaces that have been defined so far. */
+ protected HashMap<String, Namespace> namespaces;
+ protected Stack<Namespace> defaultNamespaces;
+ /** Buffer for saving characters. */
+ protected StringBuffer contentBuf;
+
+ public HandlerState(Feed feed) {
+ this.feed = feed;
+ tagstack = new Stack<SyndElement>();
+ namespaces = new HashMap<String, Namespace>();
+ defaultNamespaces = new Stack<Namespace>();
+ }
+
+
+ public Feed getFeed() {
+ return feed;
+ }
+ public FeedItem getCurrentItem() {
+ return currentItem;
+ }
+ public Stack<SyndElement> getTagstack() {
+ return tagstack;
+ }
+
+
+ public void setFeed(Feed feed) {
+ this.feed = feed;
+ }
+
+
+ public void setCurrentItem(FeedItem currentItem) {
+ this.currentItem = currentItem;
+ }
+
+ /** Returns the SyndElement that comes after the top element of the tagstack. */
+ public SyndElement getSecondTag() {
+ SyndElement top = tagstack.pop();
+ SyndElement second = tagstack.peek();
+ tagstack.push(top);
+ return second;
+ }
+
+ public StringBuffer getContentBuf() {
+ return contentBuf;
+ }
+
+
+
+
+
+}
diff --git a/src/de/danoeh/antennapod/syndication/handler/SyndHandler.java b/src/de/danoeh/antennapod/syndication/handler/SyndHandler.java
new file mode 100644
index 000000000..396f170c5
--- /dev/null
+++ b/src/de/danoeh/antennapod/syndication/handler/SyndHandler.java
@@ -0,0 +1,112 @@
+package de.danoeh.antennapod.syndication.handler;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import android.util.Log;
+
+import de.danoeh.antennapod.feed.Feed;
+import de.danoeh.antennapod.syndication.namespace.Namespace;
+import de.danoeh.antennapod.syndication.namespace.SyndElement;
+import de.danoeh.antennapod.syndication.namespace.atom.NSAtom;
+import de.danoeh.antennapod.syndication.namespace.content.NSContent;
+import de.danoeh.antennapod.syndication.namespace.itunes.NSITunes;
+import de.danoeh.antennapod.syndication.namespace.rss20.NSRSS20;
+import de.danoeh.antennapod.syndication.namespace.simplechapters.NSSimpleChapters;
+
+/** 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 = "";
+ protected HandlerState state;
+
+
+ public SyndHandler(Feed feed, TypeGetter.Type type) {
+ state = new HandlerState(feed);
+ if (type == TypeGetter.Type.RSS20) {
+ state.defaultNamespaces.push(new NSRSS20());
+ }
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName,
+ Attributes attributes) throws SAXException {
+ state.contentBuf = new StringBuffer();
+ Namespace handler = getHandlingNamespace(uri);
+ 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) {
+ String content = new String(ch, start, length);
+ state.contentBuf.append(content);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName)
+ throws SAXException {
+ Namespace handler = getHandlingNamespace(uri);
+ if (handler != null) {
+ handler.handleElementEnd(localName, state);
+ state.tagstack.pop();
+
+ }
+ state.contentBuf = null;
+
+ }
+
+ @Override
+ public void endPrefixMapping(String prefix) throws SAXException {
+ // TODO remove Namespace
+ }
+
+ @Override
+ public void startPrefixMapping(String prefix, String uri)
+ throws SAXException {
+ // Find the right namespace
+ 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.equals(NSSimpleChapters.NSTAG)) {
+ state.namespaces.put(uri, new NSSimpleChapters());
+ Log.d(TAG, "Recognized SimpleChapters namespace");
+ }
+ }
+
+ private Namespace getHandlingNamespace(String uri) {
+ Namespace handler = state.namespaces.get(uri);
+ if (handler == null && uri.equals(DEFAULT_PREFIX)
+ && !state.defaultNamespaces.empty()) {
+ handler = state.defaultNamespaces.peek();
+ }
+ return handler;
+ }
+
+ public HandlerState getState() {
+ return state;
+ }
+
+}
diff --git a/src/de/danoeh/antennapod/syndication/handler/TypeGetter.java b/src/de/danoeh/antennapod/syndication/handler/TypeGetter.java
new file mode 100644
index 000000000..7e346ca5c
--- /dev/null
+++ b/src/de/danoeh/antennapod/syndication/handler/TypeGetter.java
@@ -0,0 +1,76 @@
+package de.danoeh.antennapod.syndication.handler;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import android.util.Log;
+
+import de.danoeh.antennapod.feed.Feed;
+
+/** Gets the type of a specific feed by reading the root element. */
+public class TypeGetter {
+ private static final String TAG = "TypeGetter";
+
+ enum Type {
+ RSS20, 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;
+ try {
+ factory = XmlPullParserFactory.newInstance();
+ factory.setNamespaceAware(true);
+ XmlPullParser xpp = factory.newPullParser();
+ xpp.setInput(createReader(feed));
+ int eventType = xpp.getEventType();
+
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG) {
+ String tag = xpp.getName();
+ if (tag.equals(ATOM_ROOT)) {
+ Log.d(TAG, "Recognized type Atom");
+ return Type.ATOM;
+ } else if (tag.equals(RSS_ROOT)
+ && (xpp.getAttributeValue(null, "version")
+ .equals("2.0"))) {
+ Log.d(TAG, "Recognized type RSS 2.0");
+ return Type.RSS20;
+ } else {
+ Log.d(TAG, "Type is invalid");
+ throw new UnsupportedFeedtypeException(Type.INVALID);
+ }
+ } else {
+ eventType = xpp.next();
+ }
+ }
+
+ } catch (XmlPullParserException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ Log.d(TAG, "Type is invalid");
+ throw new UnsupportedFeedtypeException(Type.INVALID);
+ }
+
+ private Reader createReader(Feed feed) {
+ FileReader reader;
+ try {
+ reader = new FileReader(new File(feed.getFile_url()));
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ return null;
+ }
+ return reader;
+ }
+}
diff --git a/src/de/danoeh/antennapod/syndication/handler/UnsupportedFeedtypeException.java b/src/de/danoeh/antennapod/syndication/handler/UnsupportedFeedtypeException.java
new file mode 100644
index 000000000..67fbc9cc9
--- /dev/null
+++ b/src/de/danoeh/antennapod/syndication/handler/UnsupportedFeedtypeException.java
@@ -0,0 +1,29 @@
+package de.danoeh.antennapod.syndication.handler;
+
+import de.danoeh.antennapod.syndication.handler.TypeGetter.Type;
+
+public class UnsupportedFeedtypeException extends Exception {
+ private static final long serialVersionUID = 9105878964928170669L;
+ private TypeGetter.Type type;
+
+ public UnsupportedFeedtypeException(Type type) {
+ super();
+ this.type = type;
+
+ }
+
+ public TypeGetter.Type getType() {
+ return type;
+ }
+
+ @Override
+ public String getMessage() {
+ if (type == TypeGetter.Type.INVALID) {
+ return "Invalid type";
+ } else {
+ return "Type " + type + " not supported";
+ }
+ }
+
+
+}
diff --git a/src/de/danoeh/antennapod/syndication/namespace/Namespace.java b/src/de/danoeh/antennapod/syndication/namespace/Namespace.java
new file mode 100644
index 000000000..496d314a9
--- /dev/null
+++ b/src/de/danoeh/antennapod/syndication/namespace/Namespace.java
@@ -0,0 +1,23 @@
+package de.danoeh.antennapod.syndication.namespace;
+
+import org.xml.sax.Attributes;
+
+import de.danoeh.antennapod.feed.Feed;
+import de.danoeh.antennapod.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
+ * @return true if namespace handled the element, false if it ignored it
+ * */
+ public abstract void handleElementEnd(String localName, HandlerState state);
+
+}
diff --git a/src/de/danoeh/antennapod/syndication/namespace/SyndElement.java b/src/de/danoeh/antennapod/syndication/namespace/SyndElement.java
new file mode 100644
index 000000000..187312c9e
--- /dev/null
+++ b/src/de/danoeh/antennapod/syndication/namespace/SyndElement.java
@@ -0,0 +1,22 @@
+package de.danoeh.antennapod.syndication.namespace;
+
+/** Defines a XML Element that is pushed on the tagstack */
+public class SyndElement {
+ protected String name;
+ protected Namespace namespace;
+
+ public SyndElement(String name, Namespace namespace) {
+ this.name = name;
+ this.namespace = namespace;
+ }
+
+ public Namespace getNamespace() {
+ return namespace;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+
+}
diff --git a/src/de/danoeh/antennapod/syndication/namespace/atom/AtomText.java b/src/de/danoeh/antennapod/syndication/namespace/atom/AtomText.java
new file mode 100644
index 000000000..16beb277b
--- /dev/null
+++ b/src/de/danoeh/antennapod/syndication/namespace/atom/AtomText.java
@@ -0,0 +1,47 @@
+package de.danoeh.antennapod.syndication.namespace.atom;
+
+import org.apache.commons.lang3.StringEscapeUtils;
+
+import de.danoeh.antennapod.syndication.namespace.Namespace;
+import de.danoeh.antennapod.syndication.namespace.SyndElement;
+
+/** 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";
+ public static final String TYPE_XHTML = "xhtml";
+
+ private String type;
+ private String content;
+
+ public AtomText(String name, Namespace namespace, String type) {
+ super(name, namespace);
+ this.type = type;
+ }
+
+ /** Processes the content according to the type and returns it. */
+ public String getProcessedContent() {
+ if (type.equals(TYPE_HTML)) {
+ return StringEscapeUtils.unescapeHtml4(content);
+ } else if (type.equals(TYPE_XHTML)) {
+ return content;
+ } else { // Handle as text by default
+ return content;
+ }
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public void setContent(String content) {
+ this.content = content;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+
+
+}
diff --git a/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java b/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java
new file mode 100644
index 000000000..dbe1334b6
--- /dev/null
+++ b/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java
@@ -0,0 +1,146 @@
+package de.danoeh.antennapod.syndication.namespace.atom;
+
+import org.xml.sax.Attributes;
+
+import android.util.Log;
+
+import de.danoeh.antennapod.feed.Feed;
+import de.danoeh.antennapod.feed.FeedImage;
+import de.danoeh.antennapod.feed.FeedItem;
+import de.danoeh.antennapod.feed.FeedMedia;
+import de.danoeh.antennapod.syndication.handler.HandlerState;
+import de.danoeh.antennapod.syndication.namespace.Namespace;
+import de.danoeh.antennapod.syndication.namespace.SyndElement;
+import de.danoeh.antennapod.syndication.namespace.rss20.NSRSS20;
+import de.danoeh.antennapod.syndication.util.SyndDateUtils;
+
+public class NSAtom 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";
+
+ private static final String FEED = "feed";
+ private static final String TITLE = "title";
+ private static final String ENTRY = "entry";
+ private static final String LINK = "link";
+ private static final String UPDATED = "updated";
+ private static final String AUTHOR = "author";
+ private static final String CONTENT = "content";
+ private static final String IMAGE = "logo";
+ private static final String SUBTITLE = "subtitle";
+ private static final String PUBLISHED = "published";
+
+ private static final String TEXT_TYPE = "type";
+ // Link
+ private static final String LINK_HREF = "href";
+ private static final String LINK_REL = "rel";
+ private static final String LINK_TYPE = "type";
+ private static final String LINK_TITLE = "title";
+ private static final String LINK_LENGTH = "length";
+ // rel-values
+ private static final String LINK_REL_ALTERNATE = "alternate";
+ private static final String LINK_REL_ENCLOSURE = "enclosure";
+ private static final String LINK_REL_PAYMENT = "payment";
+ private static final String LINK_REL_RELATED = "related";
+ private static final String LINK_REL_SELF = "self";
+
+ /** Regexp to test whether an Element is a Text Element. */
+ private static final String isText = TITLE + "|" + CONTENT + "|" + "|"
+ + SUBTITLE;
+
+ public static final String isFeed = FEED + "|" + NSRSS20.CHANNEL;
+ public static final String isFeedItem = ENTRY + "|" + NSRSS20.ITEM;
+
+ @Override
+ public SyndElement handleElementStart(String localName, HandlerState state,
+ Attributes attributes) {
+ if (localName.equals(ENTRY)) {
+ state.setCurrentItem(new FeedItem());
+ state.getFeed().getItems().add(state.getCurrentItem());
+ state.getCurrentItem().setFeed(state.getFeed());
+ } else if (localName.matches(isText)) {
+ String type = attributes.getValue(TEXT_TYPE);
+ return new AtomText(localName, this, type);
+ } else if (localName.equals(LINK)) {
+ String href = attributes.getValue(LINK_HREF);
+ String rel = attributes.getValue(LINK_REL);
+ SyndElement parent = state.getTagstack().peek();
+ if (parent.getName().matches(isFeedItem)) {
+ if (rel == null || rel.equals(LINK_REL_ALTERNATE)) {
+ state.getCurrentItem().setLink(href);
+ } else if (rel.equals(LINK_REL_ENCLOSURE)) {
+ String strSize = attributes.getValue(LINK_LENGTH);
+ long size = 0;
+ if (strSize != null)
+ size = Long.parseLong(strSize);
+ String type = attributes.getValue(LINK_TYPE);
+ String download_url = attributes
+ .getValue(LINK_REL_ENCLOSURE);
+ state.getCurrentItem().setMedia(
+ new FeedMedia(state.getCurrentItem(), download_url,
+ size, type));
+ } else if (rel.equals(LINK_REL_PAYMENT)) {
+ state.getCurrentItem().setPaymentLink(href);
+ }
+ } else if (parent.getName().matches(isFeed)) {
+ if (rel == null || rel.equals(LINK_REL_ALTERNATE)) {
+ state.getFeed().setLink(href);
+ } else if (rel.equals(LINK_REL_PAYMENT)) {
+ state.getFeed().setPaymentLink(href);
+ }
+ }
+ }
+ return new SyndElement(localName, this);
+ }
+
+
+ @Override
+ public void handleElementEnd(String localName, HandlerState state) {
+ if (localName.equals(ENTRY)) {
+ state.setCurrentItem(null);
+ }
+
+ if (state.getTagstack().size() >= 2) {
+ AtomText textElement = null;
+ String content = state.getContentBuf().toString();
+ SyndElement topElement = state.getTagstack().peek();
+ String top = topElement.getName();
+ SyndElement secondElement = state.getSecondTag();
+ String second = secondElement.getName();
+
+ if (top.matches(isText)) {
+ textElement = (AtomText) topElement;
+ textElement.setContent(content);
+ }
+
+ if (top.equals(TITLE)) {
+
+ if (second.equals(FEED)) {
+ state.getFeed().setTitle(textElement.getProcessedContent());
+ } else if (second.equals(ENTRY)) {
+ state.getCurrentItem().setTitle(
+ textElement.getProcessedContent());
+ }
+ } else if (top.equals(SUBTITLE)) {
+ if (second.equals(FEED)) {
+ state.getFeed().setDescription(
+ textElement.getProcessedContent());
+ }
+ } else if (top.equals(CONTENT)) {
+ if (second.equals(ENTRY)) {
+ state.getCurrentItem().setDescription(
+ textElement.getProcessedContent());
+ }
+ } else if (top.equals(PUBLISHED)) {
+ if (second.equals(ENTRY)) {
+ state.getCurrentItem().setPubDate(
+ SyndDateUtils.parseRFC3339Date(content));
+ }
+ } else if (top.equals(IMAGE)) {
+ state.getFeed().setImage(new FeedImage(content, null));
+ }
+
+ }
+ }
+
+}
diff --git a/src/de/danoeh/antennapod/syndication/namespace/content/NSContent.java b/src/de/danoeh/antennapod/syndication/namespace/content/NSContent.java
new file mode 100644
index 000000000..7713eb9c3
--- /dev/null
+++ b/src/de/danoeh/antennapod/syndication/namespace/content/NSContent.java
@@ -0,0 +1,31 @@
+package de.danoeh.antennapod.syndication.namespace.content;
+
+import org.xml.sax.Attributes;
+
+import de.danoeh.antennapod.syndication.handler.HandlerState;
+import de.danoeh.antennapod.syndication.namespace.Namespace;
+import de.danoeh.antennapod.syndication.namespace.SyndElement;
+import de.danoeh.antennapod.syndication.namespace.rss20.NSRSS20;
+
+import org.apache.commons.lang3.StringEscapeUtils;
+
+public class NSContent extends Namespace {
+ public static final String NSTAG = "content";
+ public static final String NSURI = "http://purl.org/rss/1.0/modules/content/";
+
+ private static final String ENCODED = "encoded";
+
+ @Override
+ public SyndElement handleElementStart(String localName, HandlerState state,
+ Attributes attributes) {
+ return new SyndElement(localName, this);
+ }
+
+ @Override
+ public void handleElementEnd(String localName, HandlerState state) {
+ if (localName.equals(ENCODED)) {
+ state.getCurrentItem().setContentEncoded(state.getContentBuf().toString());
+ }
+ }
+
+}
diff --git a/src/de/danoeh/antennapod/syndication/namespace/itunes/NSITunes.java b/src/de/danoeh/antennapod/syndication/namespace/itunes/NSITunes.java
new file mode 100644
index 000000000..92f25f15c
--- /dev/null
+++ b/src/de/danoeh/antennapod/syndication/namespace/itunes/NSITunes.java
@@ -0,0 +1,42 @@
+package de.danoeh.antennapod.syndication.namespace.itunes;
+
+import org.xml.sax.Attributes;
+
+import de.danoeh.antennapod.feed.FeedImage;
+import de.danoeh.antennapod.syndication.handler.HandlerState;
+import de.danoeh.antennapod.syndication.namespace.Namespace;
+import de.danoeh.antennapod.syndication.namespace.SyndElement;
+
+public class NSITunes extends Namespace{
+ public static final String NSTAG = "itunes";
+ public static final String NSURI = "http://www.itunes.com/dtds/podcast-1.0.dtd";
+
+ private static final String IMAGE = "image";
+ private static final String IMAGE_TITLE = "image";
+ private static final String IMAGE_HREF = "href";
+
+ private static final String AUTHOR = "author";
+
+
+ @Override
+ public SyndElement handleElementStart(String localName, HandlerState state,
+ Attributes attributes) {
+ if (localName.equals(IMAGE) && state.getFeed().getImage() == null) {
+ FeedImage image = new FeedImage();
+ image.setTitle(IMAGE_TITLE);
+ image.setDownload_url(attributes.getValue(IMAGE_HREF));
+ state.getFeed().setImage(image);
+ }
+
+ return new SyndElement(localName, this);
+ }
+
+ @Override
+ public void handleElementEnd(String localName, HandlerState state) {
+ if (localName.equals(AUTHOR)) {
+ state.getFeed().setAuthor(state.getContentBuf().toString());
+ }
+
+ }
+
+}
diff --git a/src/de/danoeh/antennapod/syndication/namespace/rss20/NSRSS20.java b/src/de/danoeh/antennapod/syndication/namespace/rss20/NSRSS20.java
new file mode 100644
index 000000000..6dcd8daa0
--- /dev/null
+++ b/src/de/danoeh/antennapod/syndication/namespace/rss20/NSRSS20.java
@@ -0,0 +1,115 @@
+package de.danoeh.antennapod.syndication.namespace.rss20;
+
+import java.util.ArrayList;
+import java.util.Date;
+
+import de.danoeh.antennapod.feed.Feed;
+import de.danoeh.antennapod.feed.FeedImage;
+import de.danoeh.antennapod.feed.FeedItem;
+import de.danoeh.antennapod.feed.FeedMedia;
+import de.danoeh.antennapod.syndication.handler.HandlerState;
+import de.danoeh.antennapod.syndication.handler.SyndHandler;
+import de.danoeh.antennapod.syndication.namespace.Namespace;
+import de.danoeh.antennapod.syndication.namespace.SyndElement;
+import de.danoeh.antennapod.syndication.util.SyndDateUtils;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * SAX-Parser for reading RSS-Feeds
+ *
+ * @author daniel
+ *
+ */
+public class NSRSS20 extends Namespace {
+ public static final String NSTAG = "rss";
+ public static final String NSURI = "";
+
+ public final static String CHANNEL = "channel";
+ public final static String ITEM = "item";
+ public final static String TITLE = "title";
+ public final static String LINK = "link";
+ public final static String DESCR = "description";
+ public final static String PUBDATE = "pubDate";
+ public final static String ENCLOSURE = "enclosure";
+ public final static String IMAGE = "image";
+ public final static String URL = "url";
+ public final static String LANGUAGE = "language";
+
+ public final static String ENC_URL = "url";
+ public final static String ENC_LEN = "length";
+ public final static String ENC_TYPE = "type";
+
+ public final static String VALID_MIMETYPE = "audio/.*" + "|" + "video/.*";
+
+ @Override
+ public SyndElement handleElementStart(String localName, HandlerState state,
+ Attributes attributes) {
+ if (localName.equals(ITEM)) {
+ state.setCurrentItem(new FeedItem());
+ state.getFeed().getItems().add(state.getCurrentItem());
+ state.getCurrentItem().setFeed(state.getFeed());
+
+ } else if (localName.equals(ENCLOSURE)) {
+ String type = attributes.getValue(ENC_TYPE);
+ if (state.getCurrentItem().getMedia() == null
+ && (type.matches(VALID_MIMETYPE))) {
+ state.getCurrentItem().setMedia(
+ new FeedMedia(state.getCurrentItem(), attributes
+ .getValue(ENC_URL), Long.parseLong(attributes
+ .getValue(ENC_LEN)), attributes
+ .getValue(ENC_TYPE)));
+ }
+
+ } else if (localName.equals(IMAGE)) {
+ state.getFeed().setImage(new FeedImage());
+ }
+ return new SyndElement(localName, this);
+ }
+
+ @Override
+ public void handleElementEnd(String localName, HandlerState state) {
+ if (localName.equals(ITEM)) {
+ state.setCurrentItem(null);
+ } else if (state.getTagstack().size() >= 2
+ && state.getContentBuf() != null) {
+ String content = state.getContentBuf().toString();
+ SyndElement topElement = state.getTagstack().peek();
+ String top = topElement.getName();
+ SyndElement secondElement = state.getSecondTag();
+ String second = secondElement.getName();
+ if (top.equals(TITLE)) {
+ if (second.equals(ITEM)) {
+ state.getCurrentItem().setTitle(content);
+ } else if (second.equals(CHANNEL)) {
+ state.getFeed().setTitle(content);
+ } else if (second.equals(IMAGE)) {
+ state.getFeed().getImage().setTitle(IMAGE);
+ }
+ } else if (top.equals(LINK)) {
+ if (second.equals(CHANNEL)) {
+ state.getFeed().setLink(content);
+ } else if (second.equals(ITEM)) {
+ state.getCurrentItem().setLink(content);
+ }
+ } else if (top.equals(PUBDATE) && second.equals(ITEM)) {
+ state.getCurrentItem().setPubDate(
+ SyndDateUtils.parseRFC822Date(content));
+ } else if (top.equals(URL) && second.equals(IMAGE)) {
+ state.getFeed().getImage().setDownload_url(content);
+ } else if (localName.equals(DESCR)) {
+ if (second.equals(CHANNEL)) {
+ state.getFeed().setDescription(content);
+ } else {
+ state.getCurrentItem().setDescription(content);
+ }
+
+ } else if (localName.equals(LANGUAGE)) {
+ state.getFeed().setLanguage(content.toLowerCase());
+ }
+ }
+ }
+
+}
diff --git a/src/de/danoeh/antennapod/syndication/namespace/simplechapters/NSSimpleChapters.java b/src/de/danoeh/antennapod/syndication/namespace/simplechapters/NSSimpleChapters.java
new file mode 100644
index 000000000..3c7853304
--- /dev/null
+++ b/src/de/danoeh/antennapod/syndication/namespace/simplechapters/NSSimpleChapters.java
@@ -0,0 +1,43 @@
+package de.danoeh.antennapod.syndication.namespace.simplechapters;
+
+import java.util.ArrayList;
+
+import org.xml.sax.Attributes;
+
+import de.danoeh.antennapod.feed.SimpleChapter;
+import de.danoeh.antennapod.syndication.handler.HandlerState;
+import de.danoeh.antennapod.syndication.namespace.Namespace;
+import de.danoeh.antennapod.syndication.namespace.SyndElement;
+import de.danoeh.antennapod.syndication.util.SyndDateUtils;
+
+public class NSSimpleChapters extends Namespace {
+ public static final String NSTAG = "sc";
+ public static final String NSURI = "http://podlove.org/simple-chapters";
+
+ public static final String CHAPTERS = "chapters";
+ public static final String CHAPTER = "chapter";
+ public static final String START = "start";
+ public static final String TITLE = "title";
+
+ @Override
+ public SyndElement handleElementStart(String localName, HandlerState state,
+ Attributes attributes) {
+ if (localName.equals(CHAPTERS)) {
+ state.getCurrentItem().setSimpleChapters(
+ new ArrayList<SimpleChapter>());
+ } else if (localName.equals(CHAPTER)) {
+ state.getCurrentItem()
+ .getSimpleChapters()
+ .add(new SimpleChapter(SyndDateUtils
+ .parseTimeString(attributes.getValue(START)),
+ attributes.getValue(TITLE)));
+ }
+
+ return new SyndElement(localName, this);
+ }
+
+ @Override
+ public void handleElementEnd(String localName, HandlerState state) {
+ }
+
+}
diff --git a/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java b/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java
new file mode 100644
index 000000000..226a79721
--- /dev/null
+++ b/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java
@@ -0,0 +1,102 @@
+package de.danoeh.antennapod.syndication.util;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import android.util.Log;
+
+/** Parses several date formats. */
+public class SyndDateUtils {
+ private static final String TAG = "DateUtils";
+ public static final String RFC822 = "dd MMM yyyy HH:mm:ss Z";
+ /** RFC 822 date format with day of the week. */
+ public static final String RFC822DAY = "EEE, " + RFC822;
+
+ /** RFC 3339 date format for UTC dates. */
+ public static final String RFC3339UTC = "yyyy-MM-dd'T'HH:mm:ss'Z'";
+
+ /** RFC 3339 date format for localtime dates with offset. */
+ public static final String RFC3339LOCAL = "yyyy-MM-dd'T'HH:mm:ssZ";
+
+ private static ThreadLocal<SimpleDateFormat> RFC822Formatter = new ThreadLocal<SimpleDateFormat>() {
+ @Override
+ protected SimpleDateFormat initialValue() {
+ return new SimpleDateFormat(RFC822DAY, Locale.US);
+ }
+
+ };
+
+ private static ThreadLocal<SimpleDateFormat> RFC3339Formatter = new ThreadLocal<SimpleDateFormat>() {
+ @Override
+ protected SimpleDateFormat initialValue() {
+ return new SimpleDateFormat(RFC3339UTC, Locale.US);
+ }
+
+ };
+
+ public static Date parseRFC822Date(final String date) {
+ Date result = null;
+ SimpleDateFormat format = RFC822Formatter.get();
+ try {
+ result = format.parse(date);
+ } catch (ParseException e) {
+ e.printStackTrace();
+ format.applyPattern(RFC822);
+ try {
+ result = format.parse(date);
+ } catch (ParseException e1) {
+ e1.printStackTrace();
+ }
+ }
+
+ return result;
+ }
+
+ public static Date parseRFC3339Date(final String date) {
+ Date result = null;
+ SimpleDateFormat format = RFC3339Formatter.get();
+ if (date.endsWith("Z")) {
+ try {
+ result = format.parse(date);
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+ } else {
+ format.applyPattern(RFC3339LOCAL);
+ // remove last colon
+ StringBuffer buf = new StringBuffer(date.length() - 1);
+ int colonIdx = date.lastIndexOf(':');
+ for (int x = 0; x < date.length(); x++) {
+ if (x != colonIdx)
+ buf.append(date.charAt(x));
+ }
+ String bufStr = buf.toString();
+ try {
+ result = format.parse(bufStr);
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ return result;
+
+ }
+ /** Takes a string of the form [HH:]MM:SS[.mmm] and converts it to milliseconds. */
+ public static long parseTimeString(final String time) {
+ String[] parts = time.split(":");
+ long result = 0;
+ int idx = 0;
+ if (parts.length == 3) {
+ // string has hours
+ result += Integer.valueOf(parts[idx]) * 3600000;
+ idx++;
+ }
+ result += Integer.valueOf(parts[idx]) * 60000;
+ idx++;
+ result += ( Float.valueOf(parts[idx])) * 1000;
+ return result;
+ }
+}