package com.droidworks.syndication.rss;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import com.droidworks.util.AndroidLogger;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import android.sax.Element;
import android.sax.EndElementListener;
import android.sax.EndTextElementListener;
import android.sax.RootElement;
import android.sax.StartElementListener;
import android.text.TextUtils;
import com.droidworks.parsers.rss.itunes.DurationParser;
import com.droidworks.syndication.FeedAdapter;
import com.droidworks.syndication.FeedItem;
import com.droidworks.syndication.FeedItemFactory;
import com.droidworks.xml.FeedParser;
/*
* Parser for RSS 2.0 syndication feeds. Supports some itunes tags
*/
public class Rss2Parser<T extends FeedItem> extends FeedParser<T> {
private RootElement mRootElement;
private String mImageUrl;
private String mImageTitle;
private String mImageLink;
private int mImageWidth;
private int mImageHeight;
private final DurationParser mDurationParser = new DurationParser();
private FeedItem mFeedItem;
private FeedItemFactory mFeedItemFactory;
private AndroidLogger mLogger;
private static final String DEFAULT_NAMESPACE = "";
private static final String NS_ITUNES = "http://www.itunes.com/dtds/podcast-1.0.dtd";
private static final String NS_MEDIA = "http://search.yahoo.com/mrss/";
public Rss2Parser() {
super(DEFAULT_NAMESPACE);
}
public Rss2Parser(FeedItemFactory feedItemFactory, AndroidLogger logger) {
super(DEFAULT_NAMESPACE);
mFeedItemFactory = feedItemFactory;
mLogger = logger;
}
@Override
protected ContentHandler getContentHandler() {
return mRootElement.getContentHandler();
}
@Override
protected void setupNodes() {
// example pubdate
// Fri, 03 Oct 2014 01:18:04 GMT
final SimpleDateFormat df = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss zzz");
// handling Unparseable date: "2014-07-04 00:00:00.0"
final SimpleDateFormat df2 = new SimpleDateFormat("yyyy-MM-d HH:mm:ss");
mRootElement = new RootElement("rss");
Element channelNode = mRootElement.getChild("channel");
// channel sub nodes
Element titleNode = channelNode.getChild("title");
Element linkNode = channelNode.getChild("link");
Element descriptionNode = channelNode.getChild("description");
Element pubDateNode = channelNode.getChild("pubDate");
Element languageNode = channelNode.getChild("language");
Element generatorNode = channelNode.getChild("generator");
Element copyrightNode = channelNode.getChild("copyright");
Element managingEditorNode = channelNode.getChild("managingEditor");
Element categoryNode = channelNode.getChild("category");
Element ttlNode = channelNode.getChild("TTL");
Element imageNode = channelNode.getChild("image");
Element imageHeightNode = imageNode.getChild("height");
Element imageWidthNode = imageNode.getChild("width");
Element imageUrlNode = imageNode.getChild("url");
Element imageTitleNode = imageNode.getChild("title");
Element imageLinkNode = imageNode.getChild("link");
Element feedItemNode = channelNode.getChild("item");
Element feedItemTitleNode = feedItemNode.getChild("title");
Element feedItemLinkNode = feedItemNode.getChild("link");
Element feedItemCommentsNode = feedItemNode.getChild("comments");
Element feedItemPubDateNode = feedItemNode.getChild("pubDate");
Element feedItemGuidNode = feedItemNode.getChild("guid");
Element feedItemDescriptionNode = feedItemNode.getChild("description");
Element feedItemMediaNode = feedItemNode.getChild(NS_MEDIA, "content");
Element feedItemItunesSummary = feedItemNode.getChild(NS_ITUNES, "summary");
Element feedItemItunesDuration = feedItemNode.getChild(NS_ITUNES,"duration");
Element feedItemCategoryNode = feedItemNode.getChild("category");
Element feedItemEnclosureNode = feedItemNode.getChild("enclosure");
feedItemEnclosureNode.setStartElementListener(new StartElementListener() {
public void start(Attributes attrs) {
mFeedItem.setMediaUrl(attrs.getValue("url"));
mFeedItem.setMediaType(attrs.getValue("type"));
String length = attrs.getValue("length");
if (!TextUtils.isEmpty(length) && TextUtils.isDigitsOnly(length)) {
mFeedItem.setMediaSize(Integer.parseInt(length));
}
}
});
feedItemCategoryNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String text) {
mFeedItem.setCategory(text);
}
});
feedItemItunesDuration.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
mFeedItem.setItunesDuration(mDurationParser.parse(body) * 1000);
}
});
feedItemItunesSummary.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
mFeedItem.setItunesSummary(body);
}
});
feedItemMediaNode.setStartElementListener(new StartElementListener() {
public void start(Attributes attrs) {
mFeedItem.setMediaUrl(attrs.getValue("url"));
mFeedItem.setMediaType(attrs.getValue("type"));
mFeedItem.setMediaSize(Integer.parseInt((attrs.getValue("fileSize"))));
}
});
feedItemDescriptionNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
mFeedItem.setDescription(body);
}
});
feedItemGuidNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
mFeedItem.setGuid(body);
}
});
feedItemPubDateNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
try {
mFeedItem.setPubDate(df.parse(body));
}
catch (ParseException e) {
try {
mFeedItem.setPubDate(df2.parse(body));
}
catch (ParseException e2) {
mLogger.e(getLogTag(), "Error parsing pubDate", e2);
}
}
}
});
feedItemCommentsNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
mFeedItem.setComments(body);
}
});
feedItemLinkNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
mFeedItem.setLink(body);
}
});
feedItemTitleNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
mFeedItem.setTitle(body);
}
});
feedItemNode.setStartElementListener(new StartElementListener() {
public void start(Attributes attributes) {
if (mFeedItemFactory != null)
mFeedItem = mFeedItemFactory.create();
else
mFeedItem = new FeedItem();
}
});
feedItemNode.setEndElementListener(new EndElementListener() {
public void end() {
final FeedItem tmpItem = mFeedItem;
mFeedItem = null;
// try to fix a broken description
if (TextUtils.isEmpty(tmpItem.getDescription()) && !TextUtils.isEmpty(tmpItem.getItunesSummary())) {
tmpItem.setDescription(tmpItem.getItunesSummary());
}
// try to fix a broken itunes summary
if (TextUtils.isEmpty(tmpItem.getItunesSummary()) && !TextUtils.isEmpty(tmpItem.getDescription())) {
tmpItem.setItunesSummary(tmpItem.getDescription());
}
// if a podcast entry doesn't have a guid, see if we can fix it.
if (tmpItem.getGuid() == null && tmpItem.getMediaUrl() != null)
tmpItem.setGuid(tmpItem.getMediaUrl());
// only add feeditem if the pubdate isn't null,
// null pubdates cause a crash, and we can't sort
// without it..
if (tmpItem.getPubDate() != null) {
getFeed().addItem(tmpItem);
}
// notify a listener if present
if (getListener() != null) {
OnItemParsedListener listener = getListener();
listener.onItemParsed(tmpItem);
}
}
});
imageLinkNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
mImageLink = body;
}
});
imageTitleNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
mImageTitle = body;
}
});
imageUrlNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
mImageUrl = body;
}
});
imageNode.setEndElementListener(new EndElementListener() {
public void end() {
FeedAdapter.Image image = new FeedAdapter.Image(
mImageUrl, mImageTitle, mImageLink, mImageWidth,
mImageHeight);
getFeed().setFeedImage(image);
}
});
imageWidthNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
mImageWidth = Integer.parseInt(body);
}
});
imageHeightNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
mImageHeight = Integer.parseInt(body);
}
});
ttlNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
getFeed().setTTL(Integer.parseInt(body));
}
});
categoryNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
getFeed().setCategory(body);
}
});
managingEditorNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
getFeed().setManagingEditor(body);
}
});
copyrightNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
getFeed().setCopyright(body);
}
});
languageNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
getFeed().setLanguage(body);
}
});
generatorNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
getFeed().setGenerator(body);
}
});
pubDateNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
try {
getFeed().setPubDate(df.parse(body));
}
catch (ParseException e) {
mLogger.e(getClass().getCanonicalName(), "Error parsing pubDate");
}
}
});
descriptionNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
getFeed().setDescription(body);
}
});
linkNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
getFeed().setLink(body);
}
});
titleNode.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
getFeed().setTitle(body);
}
});
}
}