RSS Feed Reader
This tutorial outlines an approach for creating a RSS feed reader in Director. It is intended to demonstrate an 'object orientated' approach to creating applications in Director.
What is RSS?
RSS is a 'Web content syndication format'. Basically, it is a dialect of XML. It is used to provide content or summaries of content with links back to the full versions of the content. 'Feed readers' and 'aggregators' are used to check feeds on behalf of a user and display any updated content. For more information, see
RSS File Format
There are various RSS 'standards'. The most common (and still in use) are RSS 0.91, 0.92 and 2.0. We will focus on RSS 2.0, reasoning (like A List Apart) that it is "better to choose a standard and stick with it than to contribute to the fragmentation of the world-wide digital brainosphere".
Sample RSS document:
<?xml version='1.0' ?>
<rss version='2.0'>
<channel>
<title>Lingoworkshop</title>
<link>http://www.lingoworkshop.com/</link>
<description>News for Director Developers.</description>
<language>en-au</language>
<lastBuildDate>Sat, 04 Mar 2006 05:07:51 GMT</lastBuildDate>
<docs>http://www.lingoworkshop.com/rss.xml</docs>
<generator>Wrangler 6.4</generator>
<item>
<title>Lorem</title>
<description>Lorem ipsum dolor sit amet</description>
<link>http://www.lingoworkshop.com/Lorem</link>
<guid>http://www.lingoworkshop.com/Lorem#article1</guid>
<pubDate>Thu, 02 Mar 2006 11:09:23 GMT</pubDate>
</item>
<item>
<title>Lipsum</title>
<description>Lorem ipsum dolor sit amet</description>
<link>http://www.lingoworkshop.com/Lipsum</link>
<guid>http://www.lingoworkshop.com/Lipsum#article2</guid>
<pubDate>Thu, 02 Mar 2006 11:09:23 GMT</pubDate>
</item>
</channel>
</rss>
The feed has two main parts to it: Firstly, there is the 'channel information' part. There are various optional elements that provide information about the 'channel' (who is responsible for it, what language it is in, an do so). To keep things simple, we'll focus only on the required elements which are
- title
- description
- link
The next part of the feed is composed of any number of items (up to 15 seems to be the standard). An item is like an article of information. Typically, it is composed of a synopsis with a link back to the full story (although it could be the whole story). There are many optional elements for 'items' - but the only required element is one of title or description. The item elements that our feedreader will look for will be
- title
- description
- link
Sketching out an architecture
Our RSS Reeder is going to have a list of 'subscriptions' (each of which has a name and a url). When we select a subscription, we will get a list of headlines (the title element of the items in the feed). And when we select the headline, we will display the item description - with a link to the full story (provided we can get the link element for that item). We will also work out a system for allowing users to add to the list of subscriptions - and edit the existing ones.
Using a sketch of the GUI as a general guide, we can start to work out some of the main objects.

The SubscriptionMgr will keep a list of subscriptions. It will populate a list box with the title of each of the feeds. When the user selects a feed, the SubscriptionMgr will create a feedObject which will go and download then parse the feed (using a RSS parser). When we have downloaded and successfully parsed the feed, the feedObject will then interact with the GUI to display the feed.
Parsing the XML
Working with XML in Director can be a little awkward, so the first thing we'll do is parse the XML into Director's native PropList format. There are various ways to parse XML in Director, but this example we'll use the XML Xtra that comes with DMX2004 (the older version that came with Director MX will not work because we are going to use the MakePropList method only available in the new version).
Here's a first draft at a basic parser that parses the XML feed into a proplist. It is a fairly simple script with a single method (we could have put this parsing routine into the FeedObject script - but keeping it separate means that we could easily change the parse routine without having to dig through all the other scripts)
-- script("RSS-Parser")
on Load (me, XML)
data = [#ChannelInfo: [:], #itemsList: [], #error: 0]
XMLParser = new(xtra "xmlparser")
XMLParser.parseString(XML)
FeedRaw = XMLParser.makeProplist()
-- check we got a good parse
if voidP(FeedRaw[#child]) then
data.error = 1
return data
end if
if count(FeedRaw[#child]) < 1 then
data.error = 1
return data
end if
repeat with aChannel in FeedRaw[#child]
if aChannel[#name] = "item" then
thisItem = [:]
if count(aChannel[#child]) then
repeat with itemData in aChannel[#child]
p = symbol(itemData[#name])
v = itemData[#chardata]
thisItem.addProp(p, v)
end repeat
end if
data.itemsList.append(thisItem)
else
if voidP(aChannel[#child]) then
data.error = 1
return data
end if
FirstChannelData = aChannel[#child]
repeat with thing in FirstChannelData
if thing[#name] = "item" then
thisItem = [:]
if count(thing[#child]) then
repeat with itemData in thing[#child]
p = symbol(itemData[#name])
v = itemData[#chardata]
thisItem.addProp(p, v)
end repeat
end if
data.itemsList.append(thisItem)
else
p = symbol(thing[#name])
v = thing[#chardata]
data.channelInfo.addProp(p, v)
end if
end repeat
end if
end repeat
return data
end
Don't stress too much about how this script extracts the information from the XML (its probably not doing a very good job of it anyway). All you need to know is that if you call the Load method of this script, passing it the RSS feed (an XML formatted string) as a parameter, it will return a propList with three properties: #ChannelInfo, #itemsList, and #error. The channelInfo is a list of information about the feed (the title, url and description); The itemsList is a list of all the items. The Error property will be false if everything went well.
You can check this script is working by cutting and pasting the RSS XML shown above into a field or text member (eg member("RSS-test-xml")) and in the message window, type the following
parser = script("RSS-Parser")
put parser.Load( member("RSS-test-xml").text)
-- [#channelInfo: [#title: "Lingoworkshop", #link: "http://www.lingoworkshop.com/",...]
The next script will be used to create the 'FeedObject'. A feed
object will have methods for displaying the data we have read from
the XML feed. When we create a new feed object, we will pass a URL
for the feed as a parameter. The 'FeedObject' will then create a
NetOp object which will download the XML for us. When
the XML has been downloaded, the NetOp object will send a #NetTransactionComplete message
back to the 'FeedObject', passing the XML string as a parameter.
The 'FeedObject' then parses the XML and stores the ChannelInfo and
Items list. The 'FeedObject' will provide methods for accessing this
data.
Downloading the XML is an asynchronous operation and may take a little while (depending on the size of the XML, the user's connection speed, butterfly activity in Brazil, and so on). Therefore, we will track the current state of the feedObject. For now, we will just store the Object state (ie. 'downloading data', 'got data', 'error getting data', 'error parsing data') in a property called 'StatusStr' and provide a simple method to get this status.
Here is the script:
-- script("RSS-Feed")
property URI
property ChannelInfo
property ItemsList
property StatusStr
on new (me, aURL)
URI = aURL
-- initialise some properties (these will get
-- populated when the XML has been parsed)
ChannelInfo = [:]
ItemsList = []
-- now download the XML
StatusStr = "Getting XML"
aURL = aURL & "?"&random(the milliseconds)
NetOp = script("NetOp.transaction").new(aURL)
NetOp.AddListener(me)
NetOp.Start()
return me
end
on NetTransactionComplete (me,sender, netData)
if netData.error = 0 then
-- got the net text, now parse it
XMLStr = netData.text
RSSObj = script("RSS-Parser").new()
parseData = RSSObj.Load(XMLStr)
if parseData.error = 0 then
-- successfully parsed the XML
if parseData[#ChannelInfo].ilk = #PropList then ChannelInfo = parseData[#ChannelInfo]
if parseData[#itemsList].ilk = #List then ItemsList = parseData[#itemsList]
StatusStr = "Done"
else
-- error parsing
StatusStr = "Error parsing the XML feed"
end if
else
-- error getting the xml
StatusStr = "Error retreiving the XML feed - " & sender.GetErrorDescription(netData.error)
end if
end
on NetTransactionStatusUpdate (me,sender,data)
StatusStr = data[#state]
if StatusStr = "InProgress" then
StatusStr = "Downloading" && integer(data[#fractiondone]*100) & "%"
end if
end
-- Some Accessors for the GUI
on GetStatus (me)
return StatusStr
end
on GetFeedURL (me)
return URI
end
on GetChannelInfo (me)
return channelInfo
end
on GetTitleList (me)
rList = []
repeat with anItem in itemsList
rList.append(anItem[#title])
end repeat
return rList
end
on GetItemAt (me,p)
return itemsList[p]
end
As mentioned previously, this script uses a NetOp
object from
the Xlib to
download the XML. The XML Parser Xtra can parse URLs, however I prefer
to download the XML first (using GetNetText) since the
Network Xtras give you more information on downloads (errors, percentage
downloaded etc).
For more information on the internal workings of
the NetOp scripts, see this
article - but for the purposes of this project, just note that
they act as self-sufficient 'daemons' - objects that work in the background.
In this example, we are using the "NetOp.transaction" script
to download some text from a URL. When the transaction is complete,
the daemon sends a
NetTransactionComplete message to its listeners, passing
the results of the transaction as a parameter. The FeedObject adds
itself as a listener when it creates the NetOp.
To check everything is working so far, create a new movie, copy
the NetOp scripts from from the Xlib and
create the "RSS-Feed" and "RSS-Parser" scripts
described here. Put a basic "go to the frame"
behaviour in the first frame, and whilst the movie is playing, type
the following into the message window:
feed = script("RSS-Feed").new("http://www.lingoworkshop.com/rss.xml")
The movie needs to be playing so Director services the timeouts and the network operation. Wait a few moments (for the XML to download), and then type
put feed.GetChannelInfo()
-- [#title: "Lingoworkshop", #link: "http://www.lingoworkshop.com/", ... etc]
put feed.GetTitleList()
-- ["Using Javascript", "Standard Practice and 'Rules of Thumb'", ... etc]
The next script is the SubscriptionMgr. Basically, it keeps a list of feed urls. When a feed is selected, it creates a FeedObject for that URL. Here's the script:
property SubscriptionList
property OpenFeeds
on new (me)
SubscriptionList = me.GetSavedSubscriptions()
OpenFeeds = [:]
ActiveFeed = VOID
return me
end
on GetSubscriptions (me)
rList = []
repeat with aSite in SubscriptionList
rList.append(aSite.name)
end repeat
return rList
end
on OpenSubscription (me, pos)
-- error check the parameter
if not integerP(pos) then pos = 1
if pos < 1 or pos > SubscriptionList.count then return #ParameterError_IndexOutOfRange
-- have we already created a feedOBj for this Url?
site = SubscriptionList[pos]
feed = OpenFeeds.getAProp(site.url)
if feed.ilk <> #instance then
-- haven't created it yet
feed = script("RSS-Feed").new(site.url)
OpenFeeds.AddProp(site.url,feed)
end if
return feed
end
on OpenURL (me, url)
-- have we already created a feedOBj for this Url?
feed = OpenFeeds.getAProp(url)
if feed.ilk <> #instance then
-- haven't created it yet
feed = script("RSS-Feed").new(url)
OpenFeeds.AddProp(url,feed)
end if
return feed
end
on GetSavedSubscriptions (me)
localFile = "SW_RSS_READER.txt"
ListSaver = script("ListSaver").new()
subsList = ListSaver.ReadList(localFile)
if voidP(subsList) then
-- use defaults
subsList = me.GetDefaultList()
ListSaver.SaveList(subsList, localFile)
end if
return subsList
end
on GetDefaultList (me)
subsList = []
subsList[1] = [#name: "Lingoworkshop", #Url: "http://www.lingoworkshop.com/rss.xml"]
subsList[2] = [#name: "Director@Night", #Url: "http://www.directoratnight.com/feed/"]
subsList[3] = [#name: "Tom Higgans Blog", #Url: "http://weblogs.macromedia.com/thiggins/index.xml"]
subsList[4] = [#name: "Director Online (Articles)", #Url: "http://director-online.com/rss/director-online-articles.xml"]
subsList[5] = [#name: "Farbflash", #Url: "http://www.farbflash.de/cgi-bin/blosxom.cgi/index.rss"]
subsList[6] = [#name: "Updatestage", #Url: "http://www.updatestage.com/rss/new.rss"]
subsList[7] = [#name: "Director-Web", #Url: "http://www.mcli.dist.maricopa.edu/director/feed/new.rss"]
return subsList
end
Note - this script uses a the "ListSaver" script
from the Xlib to save the 'subscription'
list to a local file. It is not important here to understand the
internal workings of that script - just that you can save a list
like this:
ListSaver = script("ListSaver").new()
-- Read a list
someList = ListSaver.ReadList(localFile)
-- Save a list
ListSaver.SaveList(someList, localFilePath)
To check this is all working, type the following into the message window:
subMgr = script("SubscriptionMgr").new()
feed = subMgr.OpenSubscription(3)
put feed.GetChannelInfo()
-- [#title: "Tom Higgins", #link: "http://weblogs.macromedia.com/thiggins/", ... etc]
If you get an empty list when you request the channelInfo, it probably means that the XML is still being downloaded. You can do a quick check by typing "put feed.GetStatus()" into the message window.
Summary of Part 1
So far, we have created a basic RSS Reader that we can interact with via the message window. The next step is to create a graphic user interface.
Downloads
Here is a source movie containing the scripts discussed.