Wednesday, May 27, 2009

RSS Aggregator Web Part

After a long search on internet, I finally thought I have to write this RSS Aggregator Web Part that works for both Atom and RSS Formats.

First, I would like to give credit to this post by Sahil Malik which gave me the kick start.

Next, read my previous blog to understand the differences between RSS and Atom XML structure as I will be using the base of them to retrieve both blogger posts.

Now that we have our basic understanding lets jump in to the code. I am using the same css class names here since I wanted to mimic the same way as the outbox RSS Webpart. You can have your own incase you need.

We will be using 3 classes here; 1. Webpart Class, 2. Class for RSSFeed 3. Class for RSSItem and 1 JS file. Ofcourse, this code is in working condition, however its not clean. You might want to look at the redundant variable declarations.

JS: _____________________________________________________________
 
<script type="text/javascript">
function switchMenu(obj) {
var el = document.getElementById(obj);
if (el.style.display != 'none') {
el.style.display = 'none';
}
else {
el.style.display = '';
}

CollapseAllDivs(el);
}

function CollapseAllDivs(sDiv) {
var rollUpContainer = document.getElementById('rollUpContainer');
var div = rollUpContainer.getElementsByTagName('div');
for(i = 0; i < div.length; i++) {
if (div[i].id.match("rssAgg") != null) {
var getDiv = document.getElementById(div[i].id);
if (sDiv.id != getDiv.id) {
getDiv.style.display = 'none';
}
}
}
}

</script>

Variable Declarations: _______________________________________________
 
private string rssUrl;
private string feedName;
private const string constDefFeedLimitValue = "5";
private string feedLimit = constDefFeedLimitValue;
private int feedLmt;

WebPart: ________________________________________________________
 
public class RssFeedAggregator : System.Web.UI.WebControls.WebParts.WebPart
{

private string rssUrl;
private string feedName;
private const string constDefFeedLimitValue = "5";
private string feedLimit = constDefFeedLimitValue;
private int feedLmt;

public RssFeedAggregator()
{
this.ExportMode = WebPartExportMode.All;
}

protected override void RenderContents(System.Web.UI.HtmlTextWriter writer)
{
try
{
feedLmt = Convert.ToInt32(feedLimit);
}
catch (Exception)
{
feedLmt = 0;
}
RssFeed feed = new RssFeed(rssUrl);
feed.Sort(delegate(RssItem r1, RssItem r2)
{
return r2.PubDate.CompareTo(r1.PubDate);
});

int i = 0;

string parameterToken;

writer.Write("<div id=\"rollUpContainer\">");
foreach (RssItem singleRssItem in feed)
{
if (i < feedLmt)
{
parameterToken = "rssAgg" + i;

writer.Write("<div id=\"rollUp\" class=\"slm-layout-main\">");
writer.Write("<table width=\"100%\">");
writer.Write("<tr>");
writer.Write("<td width=\"100%\" align=\"left\">");
writer.Write("<table width=\"100%\">");
writer.Write("<tr><td width='100%' class=\"groupheader item medium\">");
writer.Write("<a href='" + singleRssItem.BlogLink.ToString() + "' target='_blank'>" + singleRssItem.BlogTitle + "</a>");
writer.Write("</td>");
writer.Write("</tr>");
writer.Write("<tr><td width='100%' class=\"item link-item\">");
writer.Write("<a href=\"javascript:switchMenu('" + parameterToken + "');\">" + singleRssItem.Title + "</a>");
writer.Write("</td>");
writer.Write("</tr>");
writer.Write("</table>");
writer.Write("</td>");
writer.Write("</tr>");
writer.Write("</table>");
writer.Write("</div>");

if(i > 0)
writer.Write(string.Format(@"<div id='{0}' style='display:none'>", parameterToken));
else
writer.Write(string.Format(@"<div id='{0}'>", parameterToken));

writer.Write("<table><tr><td><div class=\"description\">");
writer.Write(singleRssItem.Body);
writer.Write("</div></td></tr>");

writer.Write("</table>");

writer.Write("</div>");

i++;
}
}
writer.Write("</div>");
}

public string FeedName
{
get
{
return feedName;
}
set
{
feedName = value;
}
}

[WebBrowsable(true),
Personalizable(true),
Category("RSS Properties"),
DisplayName("RSS Feed Url(s)"),
WebDisplayName("RSS Feed Url(s)"),
Description("Use ';' Character For Multiple Feed(s)")]
public string FeedURL
{
get
{
return rssUrl;
}
set
{
rssUrl = value;
}
}

[WebBrowsable(true)]
[Personalizable(true),
Category("RSS Properties"),
DisplayName("Feed Limit"),
WebDisplayName("Feed Limit"),
Description("Feed Limit"),
DefaultValue(constDefFeedLimitValue)]
public string FeedLimit
{
get
{
return feedLimit;
}
set
{
feedLimit = value;
}
}

}

RSSFeed Class: ___________________________________________________________
 
internal class RssFeed : List<RssItem>
{
private XmlDocument xDoc;

internal RssFeed(string RssURL)
{

if (RssURL == null)
{
this.Add(new RssItem());
}
else
{
string[] rssUrlsArray = RssURL.Split(new char[] { ';' });
foreach (string url in rssUrlsArray)
{

try
{
xDoc = new XmlDocument();
XmlTextReader xRead = new XmlTextReader(url);
xDoc.Load(xRead);

if (IsRss(xDoc))
ReadRssFeed(xDoc);
else
AtomFeed(xDoc);

}
catch (Exception)
{
this.Add(new RssItem());
}
}
}
}

internal bool IsRss(XmlDocument xDoc)
{
XmlNodeList xNodes = xDoc.GetElementsByTagName("rss");
if (xNodes.Count > 0)
return true;
else
return false;

}

internal void ReadRssFeed(XmlDocument rssDoc)
{

string blogTitle = string.Empty;
string blogLink = string.Empty;

XmlNodeList xNodesHeader = rssDoc.SelectNodes("./rss/channel");
foreach (XmlNode xNode in xNodesHeader)
{
blogTitle = HttpUtility.HtmlEncode(xNode.SelectSingleNode("./title").InnerText);
blogLink = xNode.SelectSingleNode("./link").InnerText;
}

XmlNodeList xNodes = rssDoc.SelectNodes("./rss/channel/item");
foreach (XmlNode xNode in xNodes)
{
this.Add(new RssItem(xNode, blogTitle, blogLink));
}
}

internal void AtomFeed(XmlDocument atomDoc)
{
string blogTitle = string.Empty;
string blogLink = string.Empty;

XmlNodeList xNodesHeader = atomDoc.GetElementsByTagName("feed");
foreach (XmlNode xNode in xNodesHeader)
{
if (xNode["title"] != null)
blogTitle = HttpUtility.HtmlEncode(xNode["title"].InnerText);

if (xNode["link"] != null)
blogLink = xNode["link"].InnerText;

}

XmlNodeList xNodes = atomDoc.GetElementsByTagName("entry");
foreach (XmlNode xNode in xNodes)
{
this.Add(new RssItem(xNode, true, GetHrefLink(xNode), blogTitle, blogLink));
}

}

internal string GetHrefLink(XmlNode xNode)
{
string ret = string.Empty;

for (int iCount = 0; iCount < xNode.ChildNodes.Count; iCount++)
{
if (xNode.ChildNodes[iCount].Name.ToLower() == "link")
{
if (xNode.ChildNodes[iCount].Attributes["rel"].Value.ToLower() == "alternate")
{
ret = xNode.ChildNodes[iCount].Attributes["href"].Value.ToString();
}
}
}

return ret;
}
}

RSS Item Class: ____________________________________________________________
 
internal class RssItem
{
private string title;
private string href;
private string body;
private string tags;
private DateTime pubDate;
private string blogTitle;
private string blogLink;

public string Href
{
get { return href; }
}

public string Title
{
get { return title; }
}

internal RssItem()
{
title = "No Data Available At This Time.";
href = "~";
}

public string Body
{
get { return body; }
set { body = value; }
}

public string Tags
{
get { return tags; }
set { tags = value; }
}

public DateTime PubDate
{
get { return pubDate; }
set { pubDate = value; }
}

public string BlogTitle
{
get { return blogTitle; }
set { blogTitle = value; }
}

public string BlogLink
{
get { return blogLink; }
set { blogLink = value; }
}

internal RssItem(XmlNode xNode, string sBlogTitle, string sBlogLink)
{
title = xNode.SelectSingleNode("./title").InnerText;
href = xNode.SelectSingleNode("./link").InnerText;
body = FixDesc(xNode.SelectSingleNode("./description").InnerText, href);
pubDate = Convert.ToDateTime(xNode.SelectSingleNode("./pubDate").InnerText);

XmlNodeList nodeList = xNode.SelectNodes("./category");
foreach (XmlNode node in nodeList)
{
tags = tags + " " + node.InnerText;
}

BlogTitle = sBlogTitle;
blogLink = sBlogLink;
}

internal RssItem(XmlNode xNode, bool isAtom, string hrefUrl, string sBlogTitle, string sBlogLink)
{
if (xNode["title"] != null)
title = xNode["title"].InnerText;

if(!string.IsNullOrEmpty(hrefUrl))
href = hrefUrl;
else
{
if (xNode["link"] != null)
{
href = xNode["link"].Attributes["href"].Value.ToString();
}
}

if (xNode["content"] != null)
body = FixDesc(xNode["content"].InnerText, href);

if (xNode["published"] != null)
pubDate = Convert.ToDateTime(xNode["published"].InnerText);

if (xNode["category"] != null)
{
tags = xNode["category"].Attributes["term"].Value.ToString();
}

BlogTitle = sBlogTitle;
BlogLink = sBlogLink;
}

public string FixDesc(object desc, object link)
{
if (link == null desc == null) return String.Empty;

string description = desc.ToString();
Regex reg = new Regex("<.*?>", RegexOptions.Compiled);
string stripDesc = reg.Replace(description, String.Empty);

if (stripDesc.Length > 250)
{
int startPos = 250;
char[] chars = stripDesc.ToCharArray();
char c = chars[startPos];
while (!Char.IsWhiteSpace(c) && startPos < chars.Length)
{
startPos++;
c = chars[startPos];
}
stripDesc = new String(chars, 0, startPos) + "<br><a href=" + link.ToString() + " target='_blank'>More...</a>";

}
return stripDesc;
}
}

6 comments:

Anonymous said...

Hi i am new to SharePoint development. Can you guide me where to place the script in the webpart. It would be awesome if u can send me the source code @ jamesivon@gmail.com

Srini Sistla said...

you need to add the script on the page where you will have this RSS aggregator webpart.

markofsoton said...

Hi, trying to implement this in 2010 foundation build and deploys ok but doesn't prompt for a feed name and breaks the page, any ideas , thanks Mark

Srini Sistla said...

I havent yet tried this on 2010; I will check and post it back if I see anything unusual

Anna said...

I tried the webpart in 2010. When the XmlDocument tries to load the the rss feed of a list, it gives the following error: "The remote server returned an error: (401) Unauthorized." I could not solve the problem. Any help would be very much appreciated.
Anna

Srini Sistla said...

Here I go... hope this will help you - here is the code for RSS Aggregator for SharePoint 2010 -
http://codename-srini.blogspot.com/2010/07/rss-aggregator-web-part-for-sharepoint.html