From ba2d2afbbc6cbb79fc75493703425b5d6d040530 Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Fri, 13 Jul 2012 12:23:47 +0200 Subject: Renamed package and application --- .../syndication/handler/FeedHandler.java | 29 ++++ .../syndication/handler/HandlerState.java | 69 ++++++++++ .../syndication/handler/SyndHandler.java | 112 ++++++++++++++++ .../antennapod/syndication/handler/TypeGetter.java | 76 +++++++++++ .../handler/UnsupportedFeedtypeException.java | 29 ++++ .../syndication/namespace/Namespace.java | 23 ++++ .../syndication/namespace/SyndElement.java | 22 ++++ .../syndication/namespace/atom/AtomText.java | 47 +++++++ .../syndication/namespace/atom/NSAtom.java | 146 +++++++++++++++++++++ .../syndication/namespace/content/NSContent.java | 31 +++++ .../syndication/namespace/itunes/NSITunes.java | 42 ++++++ .../syndication/namespace/rss20/NSRSS20.java | 115 ++++++++++++++++ .../namespace/simplechapters/NSSimpleChapters.java | 43 ++++++ .../antennapod/syndication/util/SyndDateUtils.java | 102 ++++++++++++++ 14 files changed, 886 insertions(+) create mode 100644 src/de/danoeh/antennapod/syndication/handler/FeedHandler.java create mode 100644 src/de/danoeh/antennapod/syndication/handler/HandlerState.java create mode 100644 src/de/danoeh/antennapod/syndication/handler/SyndHandler.java create mode 100644 src/de/danoeh/antennapod/syndication/handler/TypeGetter.java create mode 100644 src/de/danoeh/antennapod/syndication/handler/UnsupportedFeedtypeException.java create mode 100644 src/de/danoeh/antennapod/syndication/namespace/Namespace.java create mode 100644 src/de/danoeh/antennapod/syndication/namespace/SyndElement.java create mode 100644 src/de/danoeh/antennapod/syndication/namespace/atom/AtomText.java create mode 100644 src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java create mode 100644 src/de/danoeh/antennapod/syndication/namespace/content/NSContent.java create mode 100644 src/de/danoeh/antennapod/syndication/namespace/itunes/NSITunes.java create mode 100644 src/de/danoeh/antennapod/syndication/namespace/rss20/NSRSS20.java create mode 100644 src/de/danoeh/antennapod/syndication/namespace/simplechapters/NSSimpleChapters.java create mode 100644 src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java (limited to 'src/de/danoeh/antennapod/syndication') 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 tagstack; + /** Namespaces that have been defined so far. */ + protected HashMap namespaces; + protected Stack defaultNamespaces; + /** Buffer for saving characters. */ + protected StringBuffer contentBuf; + + public HandlerState(Feed feed) { + this.feed = feed; + tagstack = new Stack(); + namespaces = new HashMap(); + defaultNamespaces = new Stack(); + } + + + public Feed getFeed() { + return feed; + } + public FeedItem getCurrentItem() { + return currentItem; + } + public Stack 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()); + } 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 RFC822Formatter = new ThreadLocal() { + @Override + protected SimpleDateFormat initialValue() { + return new SimpleDateFormat(RFC822DAY, Locale.US); + } + + }; + + private static ThreadLocal RFC3339Formatter = new ThreadLocal() { + @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; + } +} -- cgit v1.2.3