Sunday, February 22, 2009

Coldfusion RSS Atom Feed: CFML for Making, Creating, Generating, Valid RSS

One of the things about 'helpful blogs' that show you code is that sometimes, they show you little snippets of code. You have to basically already know everything about the subject in question, already know how to put everything together, already know how to call it, and THEN yes, that little snippet of code in the middle will be helpful. What that mostly means is that a lot of people searching will find page after page of how-to and not be able to use any of them.

I was feeling less oblivious than usual when I found Pete Freitag's old Sep 2005 blog post on 'How to Create an RSS Feed', but reading the comments made me feel better. First he left a ton of people in the dark and then he didn't respond to much of any of them. This is the honor of being a code guru I guess. Anyway, since I managed (thanks to him, and to one of his comments, and to further tweaking using the validator and trying-again) to get it working, I thought I would post the full code for my solution for others.

Possibly helpful points for new folks:
  • Generating an RSS feed (information about your website) is ONE thing; actually reading that RSS into a page is ANOTHER different thing.Two separate scripts/efforts.
  • Getting the RSS to take a user to choose their reader etc. to put that RSS into, is merely a matter of calling the file -- I mean providing the link to your file with that RSS XML in it.
  • If you have to use CFML to generate your code for the XML, then obviously, it has to be sitting in a .cfm file. It turns out it doesn't matter what the file extension is--as long as the XML inside it is recognized/validated as an RSS feed.
So we have the first part, akin to technical META headers:
  1. XML version and encoding tag
  2. RSS version and content type tag; I'm using an IF statement from Pete F there from a different one of his blog posts
  3. CHANNEL tag which opens the feed
  4. Atom link with application type tag
The next section is the RSS equiv of non-tech, content-based META headers:
  1. TITLE the actual title of the feed itself, maybe same as website
  2. LINK the link to the website the feed is representing
  3. DESCRIPTION basic info about what this site/feed is
  4. LASTBUILDDATE this is "lastupdate" info about the overall feed itself. Since mine is building from the datatable when called I put Now() on it.
  5. LANGUAGE that's pretty obvious I think
The next section is the actual content of the feed -- in my case, looping records from a CFQUERY call.

Note that any CFML code I needed to put in here, I put to the right of a basic tag -- so it would not mess up the display of the XML itself when generated to the final file, except the query cfoutput which I let take up lines so the output would display better in code. I have no idea if this is necessary, it just seemed like the thing to do... now I'm thinking, probably doesn't matter. I dunno. You can try it without that.
  1. ITEM open tag
  2. TITLE the title of this individual record, post, entry, etc.
  3. LINK this is the link to the individual record, post, entry, etc. so if someone clicked it, it would take them right to the page in question. (In my case this is behind a login but oh well. If logged in, the link works fine.)
  4. GUID with the isPermalink parameter--for me this is the same as the item above.
  5. PUBDATE the date of the individual post/entry/etc. in question.
  6. DESCRIPTION whatever description, listing, etc. you want for the item in question
  7. ITEM close tag
and when the loop is finished then
  1. CHANNEL close tag
  2. RSS close tag
and you're done.

You will want to post your code file to your remote server and then go to a validator and enter the link to it, so it can check it for you. The validator I used is: http://feedvalidator.org/

Some notes on the details of this code that might be useful:

  • It wants date formats in this format: #DateFormat(Now(), 'Ddd, dd mmm yyyy')# #TimeFormat(Now(), 'HH:MM:SS')# EST although it would prefer GMC timezone.
  • The XML must be perfect and there's a good chance that something, at least your 'description' area, might violate that once inawhile, with a character it doesn't accept. There are two different ways to deal with this, and I used them both, nested:
  • The CDATA tag, and XMLFORMAT function, which looks like this in practice:
  • <![CDATA["#XmlFormat(myquery.mydescription)#"]]>
  • although you'll note in the code below I had other info in there and a few separate calls to the function. Note that Pete's article had an error in this part, but blessedly some commenter corrected it or I would have been there all night I imagine.
The full code is below, albeit with query details changed.
The link to this code in practice is: http://www.dojopsi.com/tkr/rss1.cfm

<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<cfif cgi.user_agent contains "Mozilla"><cfheader name="Content-Type" value="text/xml"><cfelse><cfheader name="Content-Type" value="application/rss+xml"></cfif>
<channel>
<atom:link href="http://www.dojopsi.com/tkr/rss1.cfm" rel="self" type="application/rss+xml" />
<title>Remote Viewing at Ten Thousand Roads: TKR at the Dojo Psi</title>
<link>http://www.dojopsi.com/tkr/</link>
<description>Double-blind Remote Viewing Sessions Posted at TKR</description>
<cfset nowdt = "#DateFormat(Now(), 'Ddd, dd mmm yyyy')# #TimeFormat(Now(), 'HH:MM:SS')# EST">
<lastBuildDate><cfoutput>#variables.nowdt#</cfoutput></lastBuildDate>
<language>en-us</language><cfquery ... name="dslist" maxrows="50">select id, entrydt, tasktype, target, method from mytable where ... order by entrydt desc </cfquery>
<cfoutput query="dslist"><cfset target = "#trim(dslist.target)#">
<item>
<title>#XmlFormat(variables.target)#</title>
<link>http://www.dojopsi.com/tkr/rv/galleries/peanutview.cfm?dsid=#dslist.id#</link>
<guid isPermaLink="true">http://www.dojopsi.com/tkr/rv/galleries/peanutview.cfm?dsid=#dslist.id#</guid><cfset dst = "#DateFormat(dslist.entrydt, 'Ddd, dd mmm yyyy')# #TimeFormat(dslist.entrydt, 'HH:MM:SS')# EST">
<pubDate>#variables.dst#</pubDate>
<description><![CDATA["RV #XmlFormat(dslist.id)# > Type: <cfif dslist.tasktype is 2>Practice<cfelseif dslist.tasktype is 6>Mission<cfelse>Solo</cfif> > Method: #XmlFormat(dslist.method)#"]]></description>
</item>
</cfoutput>
</channel>
</rss>

[Valid RSS]
Hope this helps someone else someday.

I'll have to be doing the script to compile and display an RSS feed at some point, and I'll post it when I do.

PJ
.

3 comments:

Katherine said...

Hi,
I read the original article and being a complete newbie to XML had no idea how to implement the code. Thank you very much for providing the whole thing, including the file extension -- I very much appreciate it. :-)

Well, off to have a go! Thanks!
Katherine

Sam said...

This is extremely helpful for coldfision developers trying to create an RSS feed.

Getting the feed to validate correctly isn't covered in other articles I found, so well done.

Slow Old Dad said...

Right on about the other article "Howto Create an RSS 2.0 Feed" which was great for an intro but lacked the glue to bind it together. I'm still trying to figure out out to get my XML validated file to appear in a browser.

Your article is great!

I assume this code is before the CFFeed tag came out or is to generate the RSS feed without using it.

I have had my XML output (query driven items) from the CFFeed validated and will need to have the feed dynamically generated, via a script or chron job, every 5 or 60 minutes for 365 days a year.

Can I insert your XML specific code into the .CFM file somehow or must I choose between using EITHER CFFeed by itself OR an XML .CFM file to generate the RSS feed?

Guess I could try to create a script that 'massages' the output .XML file after CFFeed runs and prior to saving the feed for a publication.

Do you have any suggestions or links for reference?