Like everything with this blog, I wasn't entirely satisfied with ExpressionEngine's out–of–the–box solution for my RSS feeds (surprise, surprise).
Some of the features I wanted that didn't come standard with EE's RSS:
- Links and markup to display properly formatted
- Display blog name
- Display author name
- Link to the category in which the article is published
- Links to the tags with which the article is tagged
- Tracking for links to support analytics
- Two feeds; one for full articles and one for teasers
- A "Continue Reading" link for the teaser feed
The Default
EE comes with a default RSS 2.0 template that certainly gets the job done.
With just a few modifications to the default EE tags to reference my database fields and templates:
{assign_variable:master_weblog_name="default_site"}
changed to
{assign_variable:master_weblog_name="articles"}
<link>{title_permalink=site/index}</link>
changed to
<link>{title_permalink=articles/index}</link>
<description>{exp:xml_encode}{summary}{body}{/exp:xml_encode}</description>
changed to
<description>{exp:xml_encode}{intro}{remaining}{/exp:xml_encode}</description>
And this is the feed display in Google Reader (which is my preferred feed aggregator and what I used for testing this experiment):
Like I said, this gets the job done. Of course, that's not good enough for me, the perpetual perfectionist.
Formatting
The first thing I noticed about the default template was that my content lost all of the formatting associated with the various XHTML elements:
- Links (
<a>
) didn't show up as hyperlinked text. - My headings (
<h4>
,<h5>
,<h6>
) didn't show leading or font weight and size. - Code snippets (
<code>
) displayed just like the rest of the text. - Paragraphs and lists (
<p>
,<ul>
,<dl>
,<ol>
) also displayed like the rest of the text. - Images didn't display
Searching EE's Knowledge Base, I found How can I use HTML to format my RSS feed?.
I love it when solutions are simple!
So, a quick change to the RSS template, swapping the EE {exp:xml_encode}
tag with CDATA
in the <item>
<description>
element:
<description>{exp:xml_encode}{intro}{remaining}{/exp:xml_encode}</description>
changed to
<description><![CDATA[{intro}{remaining}]]></description>
This change worked exactly as I expected in Google Reader. But, as the Knowledge Base article notes, this approach may not work in all aggregators.
Blog Name
Another thing I noticed was "missing" from the default RSS: the name of my blog, "A Blog Not Limited."
The default template was instead displaying the value generated by the EE {weblog_name}
tag (contained in the <channel>
<title>
element). In this case, "Articles."
Depending on how you have EE configured and how you want your feeds to display, this default tag may be appropriate. Not for me. I wanted to display {site_name}
.
So another quick change:
<title>{exp:xml_encode}{weblog_name}{/exp:xml_encode}</title>
changed to
<title>{exp:xml_encode}{site_name}{/exp:xml_encode}</title>
I also noticed that the <channel>
<link>
element was referencing {weblog_url}
.
Again, depending on your EE configuration, this value might be appropriate. However, for mine, the {weblog_url}
references the URL to my articles, rather than the root of my site ({site_url}
), which is what I wanted.
<link>{weblog_url}</link>
changed to
<link>{site_url}</link>
Author Name
Also "missing" from my feed display: author name.
Looking at the XML, I noticed the <channel>
<dc:creator>
element and that it contained the EE {email}
tag.
Logically, I assumed that this indicated the author/creator would display, but as an email address. Yet even the email address wasn't showing in Google Reader.
So I took a peek at the RSS 2.0 specification.
I immediately noticed that the XML elements in the RSS 2.0 specification didn't mirror all of those in the default EE template. I thought, perhaps, that was the reason why author name wasn't showing.
Off to Google.
Let's Talk Namespaces
After some searching, I discovered that EE's default RSS is extended with the Dublin Core namespace.
What is a namespace? In short, a namespace allows XML to be extended to include "custom" elements that are "defined" in the namespace.
This is a very base explanation, so if you must know more, check out Keven Hemenway's Extending RSS 2.0 With Namespaces or hit Google.
Now, back to the experiment and getting the author name to display, because the namespace elements wouldn't be the source of the missing author information.
According to the namespace, <dc:creator>
should contain the name of the person responsible for content. For my purposes, this is {author}
.
As such, I modified the XML:
<dc:creator>{email}</dc:creator>
changed to
<dc:creator>{author}</dc:creator>
After waiting for Google Reader's cache to clear and grab the updated XML, I still didn't see author name displaying.
Returning to the namespace, I noticed that the example markup showed the <dc:contributor>
element contained in the <item>
element. So, I extrapolated this idea and moved the <dc:creator>
element to be nested in <item>
, rather than <channel>
.
And that worked. Again, relatively simple.
Link to Category
Every article on my blog is "organized" by category, and I wanted to not only show the category, but also make the category a link to the category page on the blog.
Since the XML was already using EE's {exp:weblog:entries}
tag, I realized it was just a matter of adding the {categories}
tag (along with a bit of hard-coded markup for formatting).
So, I updated the <item>
<description>
element:
<description>
-
<![CDATA[
-
{intro}{remaining}
-
<hr />
-
<p>Published in: {categories}<a href="{site_url}/articles/category/
{category_name}{category_url_title}/">{category_name}</a>{/categories}</p> -
]]>
</description>
Update: 8/14/2008
Turns out there is a better way to code the link to the category than what I had originally (hat tip, Ian).
Instead of using {category_name}
in the URL, {category_url_title}
is better because it:
- Prevents any issues with broken URLs in the event the category name is more than one word, because it generates the URL of the category, not just the name.
- Makes the URL component for category all lowercase, like the other URLs in the EE system.
I've modified all examples to show this change.
Links to Tags
In addition to categories, I also tag my blog articles. The tags are more specific than the categories and there can be more than one (whereas there is only one category per article).
For my RSS, I wanted to display the tags assigned to articles and make each one a link to that tag's page on the blog.
Taking the same approach as I did with categories, I added {exp:tag:tags}
to the <item>
<description>
element:
<description>
-
<![CDATA[
-
{intro}{remaining}
-
<hr />
-
<p>Published in: {categories}<a href="{site_url}/articles/category/
{category_name}{category_url_title}/">{category_name}</a>{/categories}</p> -
<p>Tagged as: {exp:tag:tags entry_id="{entry_id}" type="weblog" orderby="alpha" sort="asc" backspace="2"}<a href="{site_url}/articles/tag/{websafe_tag}/">{tag}</a>, {/exp:tag:tags}</p>
-
]]>
</description>
Note: In order to use the {exp:tag:tags}
tag, you have to purchase and install Solspace's Tag Module.
Two Different Feeds
Some folks like feeds that are just teasers, with a short excerpt or introduction to the article that they can then go to the blog to read the full text. Others like the entire article in their feed.
I wanted to offer both options.
Now that my feed was displaying the content how I wanted in Google Reader, I simply created a new template from the modified one (copy/paste to the rescue).
I named the copied template "rss_intros" and renamed the modified template "rss_articles."
Teaser Feed
In the "rss_intros" template, I updated the <channel>
<title>
element to indicate the feed is a teaser:
<title>{exp:xml_encode}{weblog_name} ~ Article Teasers{/exp:xml_encode}</title>
Article Feed
In the "rss_articles" template, I also updated the <channel>
<title>
:
<title>{exp:xml_encode}{weblog_name} ~ Articles{/exp:xml_encode}</title>
Blog Templates
Next, I updated the <head>
markup in my EE XHTML templates to include both feeds:
<link rel="alternate" type="application/rss+xml" title="Article teasers ~ A Blog Not Limited" href="/feeds/rss_intros" />
<link rel="alternate" type="application/rss+xml" title="Full article content ~ A Blog Not Limited" href="/feeds/rss_articles" />
The addition of these <link>
elements helps most browsers auto detect the feeds.
Continue Reading Teaser
While the "rss_intros" template already provides a link to the full article content via the article title, I always liked those feeds that offer a link after the introduction to read more.
A quick search in EE's forums turned up "Continue reading" link. How easy!
Working with the "rss_intros" template, I added the necessary conditional variables and markup to the <item>
<description>
element:
<description>
-
<![CDATA[
-
{intro}
-
{if remaining}<p><a href="{title_permalink=articles/index}">Continue Reading</a></p>{/if}
-
<hr />
-
<p>Published in: {categories}<a href="{site_url}/articles/category/
{category_name}{category_url_title}/">{category_name}</a>{/categories}</p> -
<p>Tagged as: {exp:tag:tags entry_id="{entry_id}" type="weblog" orderby="alpha" sort="asc" backspace="2"}<a href="{site_url}/articles/tag/{websafe_tag}/">{tag}</a>, {/exp:tag:tags}</p>
-
]]>
</description>
Tracking with Google Analytics
I use Google Analytics to track activity on A Blog Not Limited.
I was first introduced to Google Analytics when my boss, Ian, decided we would use it at work for our sites and email campaigns. I also use it on my my design portfolio.
It is a powerful, yet relatively simple tool.
Based on what I had done at work for our email campaigns (and a friendly reminder from Ian), I realized I could track inbound links from my RSS using campaign tracking.
It is really just a matter of appending a query string to my inbound links. And Google Analytics has a handy little tool to make generating the query string easy: URL Builder.
Teaser Feed
I created the following query strings for my "rss_intros" feed and appended them to the relevant links:
- Blog name link:
{exp:xml_encode}?utm_source=Teasers&utm_medium=RSS&utm_content=BlogName&utm_campaign=blog{/exp:xml_encode}
- Article title link:
{exp:xml_encode}?utm_source=Teasers&utm_medium=RSS&utm_content=ArticleTitle&utm_campaign={url_title}{/exp:xml_encode}
- Category link:
{exp:xml_encode}?utm_source=Teasers&utm_medium=RSS&utm_content=
{category_name}{category_url_title}&utm_campaign={url_title}{/exp:xml_encode}- Tag links:
{exp:xml_encode}?utm_source=Teasers&utm_medium=RSS&utm_content={websafe_tag}&utm_campaign={url_title}{/exp:xml_encode}
- Continue Reading link:
{exp:xml_encode}?utm_source=Teasers&utm_medium=RSS&utm_content=ContinueReading&utm_campaign={url_title}{/exp:xml_encode}
Article Feed
I created the following query strings for my "rss_articles" feed and appended them to the relevant links:
- Blog name link:
{exp:xml_encode}?utm_source=Articles&utm_medium=RSS&utm_content=BlogName&utm_campaign=blog{/exp:xml_encode}
- Article title link:
{exp:xml_encode}?utm_source=Articles&utm_medium=RSS&utm_content=ArticleTitle&utm_campaign={url_title}{/exp:xml_encode}
- Category link:
{exp:xml_encode}?utm_source=Articles&utm_medium=RSS&utm_content=
{category_name}{category_url_title}&utm_campaign={url_title}{/exp:xml_encode}- Tag links:
{exp:xml_encode}?utm_source=Articles&utm_medium=RSS&utm_content={websafe_tag}&utm_campaign={url_title}{/exp:xml_encode}
Note: Because these query strings contain ampersands, I added EE's {exp:xml_encode}
tag to convert the ampersand characters to entities.
Validation
With form and function complete, it was then time to validate. Searching Google, I found the Feed Validator for RSS and Atom and tested both of my feeds.
Fortunately, both were valid, but the validator offered a recommendation: Missing atom:link with rel="self".
So at the top of the feeds, following the other namespace declarations, I added:
xmlns:atom="http://www.w3.org/2005/Atom"
And then in the <channel>
element for each RSS file, I added:
<atom:link href="{site_url}/feeds/rss_intros/" rel="self" type="application/rss+xml" />
or
<atom:link href="{site_url}/feeds/rss_articles/" rel="self" type="application/rss+xml" />
Testing With Other Aggregators
While I exclusively use Google Reader, I realize not everyone does. So, I thought I would test my RSS in a couple other feed readers.
Without too much extra work or having to create a bunch of accounts, I tested Bloglines (for which I already had an account) and Safari.
Bloglines
While both of my feeds rendered as I wanted in Bloglines, I noticed this aggregator displayed additional information that I hadn't seen in Google Reader:
- Content from the
<channel>
<description>
element - Automatic display of category name from the
<item>
<dc:subject>
element - Automatic link to blog article (displayed as "Link") after RSS content
Description
From the original EE RSS default markup, <channel>
<description>
was displaying the value from {weblog_description}
, a value I hadn't set up in my EE install.
Plus, I didn't want to display the description for the "weblog." I wanted to display the description for my site … the same information I was using in the <head>
<meta>
.
So, I updated this markup:
<description>{weblog_description}</description>
changed to
<description>Online repository for Emily P. Lewis' thoughts on web design, web standards, semantics and whatever else.</description>
Category
The automatic display of the category is somewhat redundant for both my feeds, since I had added that information already. However, the automatic display didn't provide a link to that category page on my site, as what I added did.
So, I can live with it … Not to mention, I wouldn't even begin to know how to suppress this other than by removing that field entirely.
And though that field is optional in the namespace, I also considered it may provide some other value of which I'm just not aware.
Automatic Link
As for the automatic "Link," it is redundant for my Teaser feed, which I modified to have the hard-coded "Continue Reading" link.
But there's definitely nothing I can do about it, as it is pulling the information from the required <item>
<link>
element.
I can certainly live with it, and I assume folks who use Bloglines as their primary aggregator are used to this functionality, so it shouldn't pose any confusion.
Safari
Both of my RSS feeds rendered pretty much the same in Safari as in Google Reader.
The only think I noticed is that Safari, like Bloglines, automatically generates a "Read more …" link at the end of the feed.
Same conclusion as with Bloglines: nothing I can do about it and not something I'm remotely worried about.
Final RSS
After all of these changes, my RSS feeds are exactly how I want them to be in Google Reader.
Teaser Feed
Final Markup
{assign_variable:master_weblog_name="articles"}
{exp:rss:feed weblog="{master_weblog_name}"}
<?xml version="1.0" encoding="{encoding}"?>
<rss version="2.0"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
xmlns:admin="http://webns.net/mvcb/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:atom="http://www.w3.org/2005/Atom"><channel>
{exp:weblog:entries weblog="{master_weblog_name}" limit="10" rdf="off" dynamic_start="on" disable="member_data|trackbacks|pagination"}
<title>{exp:xml_encode}{site_name}{/exp:xml_encode}</title>
<link>{site_url}{exp:xml_encode}?utm_source=Teasers&utm_medium=RSS&utm_content=BlogName&utm_campaign=blog{/exp:xml_encode}</link>
<description>Online repository for Emily P. Lewis' thoughts on web design, web standards, semantics and whatever else.</description>
<dc:language>{weblog_language}</dc:language>
<dc:rights>Copyright {gmt_date format="%Y"}</dc:rights>
<dc:date>{gmt_date format="%Y-%m-%dT%H:%i:%s%Q"}</dc:date>
<admin:generatorAgent rdf:resource="http://expressionengine.com/" />
<atom:link href="{site_url}/feeds/rss_intros" rel="self" type="application/rss+xml" />
<item>
<title>{exp:xml_encode}{title}{/exp:xml_encode}</title>
<dc:creator>{author}</dc:creator>
<link>{title_permalink=articles/index}{exp:xml_encode}?utm_source=Teasers&utm_medium=RSS&utm_content=ArticleTitle&utm_campaign={url_title}{/exp:xml_encode}</link>
<guid>{title_permalink=articles/index}{exp:xml_encode}#When:{gmt_entry_date format="%H:%i:%sZ"}?utm_source=Teasers&utm_medium=RSS&utm_content=ArticleTitle&utm_campaign={url_title}{/exp:xml_encode}</guid>
<description>
<![CDATA[
{intro}
{if remaining}<p><a href="{title_permalink=articles/index}{exp:xml_encode}?utm_source=Teasers&utm_medium=RSS&utm_content=ContinueReading&utm_campaign={url_title}{/exp:xml_encode}">Continue Reading</a></p>{/if}
<hr />
<p>Published in: {categories}<a href="{site_url}/articles/category/
{category_name}{category_url_title}/{exp:xml_encode}?utm_source=Teasers&utm_medium=RSS&utm_content={category_name}&utm_campaign={url_title}{/exp:xml_encode}">{category_name}</a>{/categories}</p><p>Tagged as: {exp:tag:tags entry_id="{entry_id}" type="weblog" orderby="alpha" sort="asc" backspace="2"}<a href="{site_url}/articles/tag/{websafe_tag}/{exp:xml_encode}?utm_source=Teasers&utm_medium=RSS&utm_content={websafe_tag}&utm_campaign={url_title}{/exp:xml_encode}">{tag}</a>, {/exp:tag:tags}</p>
]]>
</description>
<dc:subject>{exp:xml_encode}{categories}{category_name}{/categories}{/exp:xml_encode}</dc:subject>
<dc:date>{gmt_entry_date format="%Y-%m-%dT%H:%i:%s%Q"}</dc:date>
</item>
{/exp:weblog:entries}
</channel>
</rss>
{/exp:rss:feed}
Article Feed
Final Markup
{assign_variable:master_weblog_name="articles"}
{exp:rss:feed weblog="{master_weblog_name}"}
<?xml version="1.0" encoding="{encoding}"?>
<rss version="2.0"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
xmlns:admin="http://webns.net/mvcb/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:atom="http://www.w3.org/2005/Atom"><channel>
{exp:weblog:entries weblog="{master_weblog_name}" limit="10" rdf="off" dynamic_start="on" disable="member_data|trackbacks|pagination"}
<title>{exp:xml_encode}{site_name}{/exp:xml_encode}</title>
<link>{site_url}{exp:xml_encode}?utm_source=Articles&utm_medium=RSS&utm_content=BlogName&utm_campaign=blog{/exp:xml_encode}</link>
<description>Online repository for Emily P. Lewis' thoughts on web design, web standards, semantics and whatever else.</description>
<dc:language>{weblog_language}</dc:language>
<dc:rights>Copyright {gmt_date format="%Y"}</dc:rights>
<dc:date>{gmt_date format="%Y-%m-%dT%H:%i:%s%Q"}</dc:date>
<admin:generatorAgent rdf:resource="http://expressionengine.com/" />
<atom:link href="{site_url}/feeds/rss_articles" rel="self" type="application/rss+xml" />
<item>
<title>{exp:xml_encode}{title}{/exp:xml_encode}</title>
<dc:creator>{author}</dc:creator>
<link>{title_permalink=articles/index}{exp:xml_encode}?utm_source=Articles&utm_medium=RSS&utm_content=ArticleTitle&utm_campaign={url_title}{/exp:xml_encode}</link>
<guid>{title_permalink=articles/index}{exp:xml_encode}#When:{gmt_entry_date format="%H:%i:%sZ"}?utm_source=Articles&utm_medium=RSS&utm_content=ArticleTitle&utm_campaign={url_title}{/exp:xml_encode}</guid>
<description>
<![CDATA[
{intro}{remaining}
<hr />
<p>Published in: {categories}<a href="{site_url}/articles/category/
{category_name}{category_url_title}/{exp:xml_encode}?utm_source=Articles&utm_medium=RSS&utm_content={category_name}&utm_campaign={url_title}{/exp:xml_encode}">{category_name}</a>{/categories}</p><p>Tagged as: {exp:tag:tags entry_id="{entry_id}" type="weblog" orderby="alpha" sort="asc" backspace="2"}<a href="{site_url}/articles/tag/{websafe_tag}/{exp:xml_encode}?utm_source=Articles&utm_medium=RSS&utm_content={websafe_tag}&utm_campaign={url_title}{/exp:xml_encode}">{tag}</a>, {/exp:tag:tags}</p>
]]>
</description>
<dc:subject>{exp:xml_encode}{categories}{category_name}{/categories}{/exp:xml_encode}</dc:subject>
<dc:date>{gmt_entry_date format="%Y-%m-%dT%H:%i:%s%Q"}</dc:date>
</item>
{/exp:weblog:entries}
</channel>
</rss>
{/exp:rss:feed}
Happy coding!
♥ Share the Love