
Salesforce RSS Reader for Visualforce
So what is RSS and what is the use case for including it inside of a Visualforce pages? If you’ve ever used a news reader application, it was powered by the RSS feeds of various websites or services. RSS, aka Really Simple Syndication, uses a standard XML format (RSS 2.0 spec will be used in this example) to provide access to data from applications such as blogs, stock quotes, weather websites, news headlines, and many more. Being able to bring data from RSS feeds into your Visualforce pages gives you the ability to aggregate information from multiple sources, and because those resources use the same standard, you don’t have to develop independent processes for each resource. My goal, is to provide you with a utility that will make this integration even easier.
A few things to note: You are confined by the call out limits of Salesforce (as of 03/27/2012 it is a max of 10 web service calls per transaction, essentially a max of 10 RSS feeds per page). Second, you need to manually configure the “Remote Site Settings” of your org to work with each feed resource.
Before we started, I would just like to point out a few handy services that make their content available through RSS feeds:
News feeds
- Bing News (which I will cover in this example) - http://api.bing.com/rss.aspx?Source=News&Market=en-US&Version=2.0&Query=
- Google News - http://news.google.com/news?pz=1&cf=all&ned=us&hl=en&output=rss
- CNN Top Stories - http://rss.cnn.com/rss/cnn_topstories.rss
- ABC Top Stories - http://feeds.abcnews.com/abcnews/topstories
- MSNBC Top Stories - http://pheedo.msnbc.msn.com/id/3032091/device/rss
Stock Quotes (most will allow you to get news feeds for specific stock symbols)
- Nasdaq - http://articlefeeds.nasdaq.com/nasdaq/categories?category=Stocks
- Yahoo Stocks - http://news.yahoo.com/rss/stock-markets
Weather
- Weather.com National Weather - http://rss.weather.com/rss/national/rss_nwf_rss.xml?cm_ven=NWF&cm_cat=rss&par=NWF_rss
Other
- Salesforce AppExchange New Listing - http://appexchange.salesforce.com/services/xml/NewListings
- Salesforce Developer Blog - http://feeds.feedburner.com/SforceBlog?format=xml
Now, onto the fun part.
Salesforce.com Apex RSS Parser Utility
The purpose of this Apex utility class is to allow you to parse the XML data of an RSS feed using its own standards and store that data into a wrapper class that you can then use to display the data however you wish (e.g. in a Visualforce page). This class is built so that it accepts a single parameter, the RSS feed endpoint, below is the code including a test unit.
public class RSS {
public class channel {
public String title {get;set;}
public String link {get;set;}
public String description {get;set;}
public String author {get;set;}
public String category {get;set;}
public String copyright {get;set;}
public String docs {get;set;}
public RSS.image image {get;set;}
public list<RSS.item> items {get;set;}
public channel() {
items = new list<RSS.item>();
}
}
public class image {
public String url {get;set;}
public String title {get;set;}
public String link {get;set;}
}
public class item {
public String title {get;set;}
public String guid {get;set;}
public String link {get;set;}
public String description {get;set;}
public String pubDate {get;set;}
public String source {get;set;}
public Date getPublishedDate() {
Date result = (pubDate != null) ? Date.valueOf(pubDate.replace('T', ' ').replace('Z','')) : null;
return result;
}
public DateTime getPublishedDateTime() {
DateTime result = (pubDate != null) ? DateTime.valueOf(pubDate.replace('T', ' ').replace('Z','')) : null;
return result;
}
}
public static RSS.channel getRSSData(string feedURL) {
HttpRequest req = new HttpRequest();
req.setEndpoint(feedURL);
req.setMethod('GET');
Dom.Document doc = new Dom.Document();
Http h = new Http();
if (!Test.isRunningTest()){
HttpResponse res = h.send(req);
doc = res.getBodyDocument();
} else {
String xmlString = '<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:os="http://a9.com/-/spec/opensearch/1.1/"><channel><title>salesforce.com - Bing News</title><link>http://www.bing.com/news</link><description>Search Results for salesforce.com at Bing.com</description><category>News</category><os:totalResults>3370</os:totalResults><os:startIndex>0</os:startIndex><os:itemsPerPage>10</os:itemsPerPage><os:Query role="request" searchTerms="salesforce.com" /><copyright>These XML results may not be used, reproduced or transmitted in any manner or for any purpose other than rendering Bing results within an RSS aggregator for your personal, non-commercial use. Any other use requires written permission from Microsoft Corporation. By using these results in any manner whatsoever, you agree to be bound by the foregoing restrictions.</copyright><image><url>http://www.bing.com/s/a/rsslogo.gif</url><title>Bing</title><link>http://www.bing.com/news</link></image><docs>http://www.rssboard.org/rss-specification</docs><item><title>Salesforce.com Makes Friends With CIOs - Information Week</title><guid>http://informationweek.com/news/cloud-computing/software/232602782</guid><link>http://informationweek.com/news/cloud-computing/software/232602782</link><description>Parade of CIOs at CloudForce shows how social networking inroads are making Salesforce.com a larger part of the IT infrastructure. Salesforce.com isn't just for sales forces anymore. Its Chatter app has opened a social networking avenue into the enterprise ...</description><pubDate>2012-03-19T15:21:47Z</pubDate><source>Information Week</source></item></channel></rss>';
doc.load(xmlString);
}
Dom.XMLNode rss = doc.getRootElement();
//first child element of rss feed is always channel
Dom.XMLNode channel = rss.getChildElements()[0];
RSS.channel result = new RSS.channel();
list<RSS.item> rssItems = new list<RSS.item>();
//for each node inside channel
for(Dom.XMLNode elements : channel.getChildElements()) {
if('title' == elements.getName()) {
result.title = elements.getText();
}
if('link' == elements.getName()) {
result.link = elements.getText();
}
if('description' == elements.getName()) {
result.description = elements.getText();
}
if('category' == elements.getName()) {
result.category = elements.getText();
}
if('copyright' == elements.getName()) {
result.copyright = elements.getText();
}
if('docs' == elements.getName()) {
result.docs = elements.getText();
}
if('image' == elements.getName()) {
RSS.image img = new RSS.image();
//for each node inside image
for(Dom.XMLNode xmlImage : elements.getChildElements()) {
if('url' == xmlImage.getName()) {
img.url = xmlImage.getText();
}
if('title' == xmlImage.getName()) {
img.title = xmlImage.getText();
}
if('link' == xmlImage.getName()) {
img.link = xmlImage.getText();
}
}
result.image = img;
}
if('item' == elements.getName()) {
RSS.item rssItem = new RSS.item();
//for each node inside item
for(Dom.XMLNode xmlItem : elements.getChildElements()) {
if('title' == xmlItem.getName()) {
rssItem.title = xmlItem.getText();
}
if('guid' == xmlItem.getName()) {
rssItem.guid = xmlItem.getText();
}
if('link' == xmlItem.getName()) {
rssItem.link = xmlItem.getText();
}
if('description' == xmlItem.getName()) {
rssItem.description = xmlItem.getText();
}
if('pubDate' == xmlItem.getName()) {
rssItem.pubDate = xmlItem.getText();
}
if('source' == xmlItem.getName()) {
rssItem.source = xmlItem.getText();
}
}
//for each item, add to rssItem list
rssItems.add(rssItem);
}
}
//finish RSS.channel object by adding the list of all rss items
result.items = rssItems;
return result;
}
static testMethod void RSSTest() {
RSS.channel chan = RSS.getRSSData('test');
Date pDate = chan.items[0].getPublishedDate();
DateTime pDateTime = chan.items[0].getPublishedDateTime();
}
}
Visualforce RSS Reader
Now let’s say that you want to use the parser utility to display data in a Visualforce page. The usage is very simple, as you will see below. We will begin by building a controller for the page.
public class RSSNewsReader {
public String rssQuery {get;set;}
private String rssURL {get;set;}
public RSSNewsReader() {
rssURL = 'http://api.bing.com/rss.aspx?Source=News&Market=en-US&Version=2.0&Query=';
rssQuery = 'salesforce.com'; //default on load
}
public RSS.channel getRSSFeed() {
return RSS.getRSSData(rssURL + EncodingUtil.urlEncode(rssQuery,'UTF-8'));
}
static testMethod void RSSNewsReaderTest() {
RSSNewsReader con = new RSSNewsReader();
RSS.channel rssFeed = con.getRSSFeed();
}
}
In the above example, I’m using the Bing news feed url, which accepts a query parameter. This allows me to accept user input in order to dynamically modify the feed end point and get the new data from the getRSSFeed() method. Below is the Visualforce page that goes with this controller.
<apex:page controller="RSSNewsReader" sidebar="false" showHeader="false" cache="false">
<style>
.ajax-loader {
background: url({!$Resource.ajax_loader});
display: inline-block;
width: 16px;
height: 11px;
}
</style>
<apex:image value="{!$Resource.ajax_loader}" style="visibility: hidden;" />
<apex:pageBlock id="rssBlock" tabStyle="Lead">
<apex:form style="padding-bottom: 10px;">
<span>Bing News Query (Enter a Topic): </span>
<apex:inputText value="{!rssQuery}" />
<apex:commandButton value="Search Bing News" reRender="rssBlock" status="searchStatus" />
<apex:actionStatus id="searchStatus" startStyleClass="ajax-loader" />
</apex:form>
<apex:pageBlockSection title="Channel" columns="2">
<apex:pageBlockSectionItem >
<apex:outputLabel value="title" />
<apex:outputText value="{!RSSFeed.title}" />
</apex:pageBlockSectionItem>
<apex:pageBlockSectionItem >
<apex:outputLabel value="link" />
<apex:outputText value="{!RSSFeed.link}" />
</apex:pageBlockSectionItem>
<apex:pageBlockSectionItem >
<apex:outputLabel value="description" />
<apex:outputText value="{!RSSFeed.description}" />
</apex:pageBlockSectionItem>
<apex:pageBlockSectionItem >
<apex:outputLabel value="category" />
<apex:outputText value="{!RSSFeed.category}" />
</apex:pageBlockSectionItem>
<apex:pageBlockSectionItem >
<apex:outputLabel value="docs" />
<apex:outputText value="{!RSSFeed.docs}" />
</apex:pageBlockSectionItem>
</apex:pageBlockSection>
<apex:pageBlockSection columns="1">
<apex:pageBlockSectionItem >
<apex:outputLabel value="copyright" />
<apex:outputText value="{!RSSFeed.copyright}" />
</apex:pageBlockSectionItem>
</apex:pageBlockSection>
<apex:pageBlockSection title="Image" columns="2">
<apex:pageBlockSectionItem >
<apex:outputLabel value="title" />
<apex:outputText value="{!RSSFeed.image.title}" />
</apex:pageBlockSectionItem>
<apex:pageBlockSectionItem >
<apex:outputLabel value="url" />
<apex:outputText value="{!RSSFeed.image.url}" />
</apex:pageBlockSectionItem>
<apex:pageBlockSectionItem >
<apex:outputLabel value="link" />
<apex:outputText value="{!RSSFeed.image.link}" />
</apex:pageBlockSectionItem>
<apex:pageBlockSectionItem >
<apex:outputLabel value="url image" />
<apex:image value="{!RSSFeed.image.url}" />
</apex:pageBlockSectionItem>
</apex:pageBlockSection>
<apex:pageBlockSection title="Items" columns="1">
<apex:pageBlockTable value="{!RSSFeed.items}" var="i">
<apex:column headerValue="title">
<apex:outputLink value="{!i.link}" target="_blank">{!i.title}</apex:outputLink>
</apex:column>
<apex:column headerValue="description" value="{!i.description}"/>
<apex:column headerValue="pubDate" style="width: 140px;">
<apex:outputText value="{0,date,MM/dd/yy h:mm:ss a}" >
<apex:param value="{!i.PublishedDateTime}" />
</apex:outputText>
</apex:column>
<apex:column headerValue="source" value="{!i.source}" style="width: 140px;" />
</apex:pageBlockTable>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:page>
To see the news reader in action, visit the Salesforce RSS Reader for Visualforce demo page. As always, I highly encourage your feedback. Put this solution to the harshest of tests, break it, and we will work on fixing it together.