For reasons I won’t go into (but which are probably obvious when you look at the code) I needed a simple way of importing RSS feeds into Salesforce. Although I have seen others, I decided to create a new one using XMLStreamReader that includes test coverage and which is installable via an unmanaged package.
Why XMLStreamReader instead of the oft used XMLNode? Well, in the case of RSS, XMLStreamReader is great because it’s a read-only interface, which is all I want to do. This keeps everything super fast and efficient, and pretty easy to test.
The heavy lifting is done in the BlogLog_RssReader class. As a developer, you simply send the raw text representation of the RSS XML into the read method, and it returns a List of Blog_Entry__c objects.
Here’s the meat:
public List<Blog_Entry__c> read(String document) { List<Blog_Entry__c> ret = new List<Blog_Entry__c>(); boolean isSafeToGetNextXmlElement = true; XmlStreamReader reader = new XmlStreamReader(document); while(isSafeToGetNextXmlElement) { if (reader.getEventType() == XmlTag.START_ELEMENT) { System.debug('^^^^' + reader.getLocalName()); if ('item' == reader.getLocalName()) { Blog_Entry__c item = parseItem(reader); ret.add(item); } } // Always use hasNext() before calling next() to confirm // that we have not reached the end of the stream if (reader.hasNext()) { reader.next(); } else { isSafeToGetNextXmlElement = false; break; } } return ret; } Blog_Entry__c parseItem(XmlStreamReader reader) { Blog_Entry__c ret = new Blog_Entry__c(); boolean isSafeToGetNextXmlElement = true; while(isSafeToGetNextXmlElement) { if (reader.getEventType() == XmlTag.END_ELEMENT && 'item' == reader.getLocalName()) { isSafeToGetNextXmlElement = false; break; } if (reader.getEventType() == XmlTag.START_ELEMENT) { System.debug('****' + reader.getLocalName() + '~~~~' + reader.getNamespace()); if ('title' == reader.getLocalName() && reader.getNamespace() == null) { String title = parseString(reader); ret.Title__c = title; } if ('link' == reader.getLocalName() && reader.getNamespace() == null) { String link = parseString(reader); ret.Link__c = link; } if ('origLink' == reader.getLocalName()) { String link = parseString(reader); ret.Link__c = link; } if ('creator' == reader.getLocalName() ) { String author = parseString(reader); ret.Author__c = author; } if ('category' == reader.getLocalName() ) { String category = parseString(reader); if (ret.Category__c != null) { ret.Category__c = ret.Category__c + ', ' + category; } else { ret.Category__c = category; } if (ret.Category__c.length() > 250) { ret.Category__c = ret.Category__c.substring(0,249); } } if ('pubDate' == reader.getLocalName() ) { String pubDate = parseString(reader); ret.Published__c = convertRSSDateStringToDate(pubDate); } if ('description' == reader.getLocalName() ) { String description = parseString(reader); ret.Lead_Copy__c = description; } } // Always use hasNext() before calling next() to confirm // that we have not reached the end of the stream if (reader.hasNext()) { reader.next(); } else { isSafeToGetNextXmlElement = false; break; } } return ret; } String parseString(XmlStreamReader reader) { String ret = ''; boolean isSafeToGetNextXmlElement = true; while(isSafeToGetNextXmlElement) { System.debug('****EVENTTYPE' + reader.getEventType()); if (reader.getEventType() == XmlTag.END_ELEMENT) { break; } else if (reader.getEventType() == XmlTag.CHARACTERS) { System.debug('****Characters |' + reader.getText() + '|'); ret = ret + reader.getText(); } else if (reader.getEventType() == XmlTag.CDATA) { System.debug('****CDATA'); ret = reader.getText(); } // Always use hasNext() before calling next() to confirm // that we have not reached the end of the stream if (reader.hasNext()) { reader.next(); } else { isSafeToGetNextXmlElement = false; break; } } return ret.trim(); }
You get the idea. Read method takes the whole document and looks for an item. ParseItem looks for the individual fields I care about, and ParseString gets the code out of those fields. Good times.
Code is on Github. As always, you can get a free Developer Edition with a few quick clicks.
Improvements? Comments? Let me know.