I’ve been thinking for a while that we could use feeds as a way to “publish” data from our applications. In house, this could be things like error reports from our customers. For our customer applications, it could be used for things like reminders.
So I sat down with Developing Feeds with RSS and Atom and wrote some code to generate feeds. I picked RSS 2.0 since it looked like the simplest but similar code could be used for other standards.
A feed is simply an XML document so it’s not too hard. I patterned the code after an example in the book of the Perl XML::RSS module.
Here’s an example of how you use it:
rss = Rss2() rss.Channel(title: 'My Channel', link: 'http://abc.com/mychannel.html', description: 'my channel of stuff') rss.AddItem(title: 'My Item', description: 'my item of stuff') Print(rss.ToString())
This would produce:
<!--?xml version="1.0" encoding="us-ascii"?-->My Channel http://abc.com/mychannel.htmlmy channel of stuffMy Item my item of stuff https://suneido.com/20071009_172526752
You can validate this using the W3C Feed Validator. The code checks for required elements.
Here is the code:
Rss2
class { New() { .items = [] } channel: false Channel(@args) { if .channel isnt false throw "Rss2: only one channel allowed" .channel = new .channel_element .elements(.channel, args) } channel_element: class { Name: 'channel' Elements: (title, link, description, language, copyright, managingEditor, webMaster, pubDate, lastBuildDate, category, generator, docs, cloud, ttl, image, rating, textinput, skipDays, skipHours) Required: (title, link, description) } image: false Image(@args) { if .image isnt false throw "Rss2: only one image allowed" .image = new .image_element if not args.Member?(#link) and .channel isnt false args = args.Copy().Add(.channel.link, at: #link) .elements(.image, args) } image_element: class { Name: 'image' Elements: (url, title, link, width, height) Required: (url, title, link) } textInput: false TextInput(@args) { if .textInput isnt false throw "Rss2: only one textInput allowed" .textInput = new .textInput_element .elements(.textInput, args) } textInput_element: class { Name: 'textInput' Elements: (title, description, name, link) Required: (title, description, name, link) } AddItem(@args) { .items.Add(item = new .item_element) .elements(item, args) if not item.Member?(#title) and not item.Member?(#description) throw "Rss2: item must have either title or description" } item_element: class { Name: 'item' Elements: (title, link, description, author, category, comments, enclosure, guid, pubDate, source) Required: () } ToString() { return '<!--?xml version="1.0" encoding="us-ascii"?-->\n' $ Xml('rss', version: '2.0') { '\n' $ .xml(.channel, .xml(.image) $ .xml(.textInput) $ .items_tostring()) } } items_tostring() { s = '' for item in .items { if not item.Member?(#guid) item.guid = .guid() s $= .xml(item) } return s } guid() { return 'https://suneido.com/' $ Display(Timestamp()).Substr(1).Tr('.', '_') } elements(ob, args) { for m in args.Members() if ob.Elements.Has?(m) ob[m] = args[m] else throw 'Rss2: invalid element: ' $ ob.Name $ '/' $ m for m in ob.Required if not ob.Member?(m) throw 'Rss2: missing element: ' $ ob.Name $ '/' $ m } xml(ob, body = '') { if ob is false return '' s = '' for m in ob.Elements if ob.Member?(m) s $= Xml(m, XmlEntityEncode(ob[m])) $ '\n' return Xml(ob.Name, '\n' $ s $ body) $ '\n' } }
The next step is to hook this up to HttpServer so you can actually access the feeds. And perhaps adding a caching mechanism so the feed doesn’t have to be regenerated for every request.