Skip to main content
  1. Dispatches/

Human Readable RSS Feeds With Pelican

·692 words·4 mins
Articles Python Rss Pelican
Daniel Andrlik
Author
Daniel Andrlik lives in the suburbs of Philadelphia. By day he manages product teams. The rest of the time he is a podcast host and producer, writer of speculative fiction, a rabid reader, and a programmer.

A few months ago, I came across this post by Simone Silvestroni explaining how they implemented a styled and human-readable RSS feed for their Jekyll-powered blog. I really liked the idea and wanted to implement it on my own site for browsers that don’t automatically offer to subscribe to a given feed. Given that Silvestroni’s solution seemed to be straightforward enough, I set out to make the change for this Pelican-powered blog.

The problem I encountered almost immediately was that feed generation in Pelican is not done via templates, but rather through the feedgenerator module. As a result, the only way to alter the feed structure is to deploy a plugin. Unfortunately, I couldn’t find any such plugin already present within the ecosystem, so I set the project aside for the foreseeable future.

Until yesterday, when I decided to quickly write a plugin to do just that.

It’s called pelican-feed-stylesheet, and it enables you to specify a url for a XLS file that should be referenced as the XML stylesheet for your feed. It does this by following Pelican’s recommended approach of creating a new Writer class and using the get_writer signal to attach it to the publication process.

To use this plugin, first install it:

python -m pip install pelican-feed-stylesheet

Then add the following to the bottom of your pelicanconf.py or publishconf.py file:

FEED_STYLESHEET = "/YOUR_URL_HERE.xls"

PLUGINS.append("pelican_feed_stylesheet")

Now, of course, for this to work, you need to make your XLS file. This file is a presentation layer, so this work really belongs in your theme as opposed to the plugin itself, and you can use XPath queries to pull in relevant information from your feed so that you don’t have to duplicate data.

I used Silvestroni’s XLS file, as a starting point for mine, which is as good as any. My feed is structured slightly differently, so it required different XPath queries to retrieve information, and I customized the CSS so that the feed would be styled to match the rest of the site theme.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
  <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
  <xsl:template match="/">
    <html xmlns="http://www.w3.org/1999/xhtml">
      <head>
        <title><xsl:value-of select="feed/title"/> RSS Feed</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
        <meta charset="UTF-8"/>
        <link rel="stylesheet" href="/theme/stylesheet/fontcss.css"/>
        <link rel="stylesheet" href="/theme/stylesheet/feed.css"/>
      </head>
      <body>
        <header>
          <h1><svg class="rss-logo" xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 256 256"><defs><linearGradient x1=".085" y1=".085" x2=".915" y2=".915" id="a"><stop offset="0" stop-color="#E3702D"/><stop offset=".107" stop-color="#EA7D31"/><stop offset=".35" stop-color="#F69537"/><stop offset=".5" stop-color="#FB9E3A"/><stop offset=".702" stop-color="#EA7C31"/><stop offset=".887" stop-color="#DE642B"/><stop offset="1" stop-color="#D95B29"/></linearGradient></defs><rect width="256" height="256" rx="55" ry="55" fill="#CC5D15"/><rect width="246" height="246" rx="50" ry="50" x="5" y="5" fill="#F49C52"/><rect width="236" height="236" rx="47" ry="47" x="10" y="10" fill="url(#a)"/><circle cx="68" cy="189" r="24" fill="#FFF"/><path d="M160 213h-34a82 82 0 0 0-82-82V97a116 116 0 0 1 116 116z" fill="#FFF"/><path d="M184 213A140 140 0 0 0 44 73V38a175 175 0 0 1 175 175z" fill="#FFF"/></svg><xsl:value-of select="atom:feed/atom:title"/></h1>
          <div class="aboutfeeds">
            <p>This is a web feed, also known as an RSS feed. To know more read <a href="https://aboutfeeds.com/" target="_blank"><strong>About Feeds</strong> <small>↗︎</small></a> by Matt Webb.</p>
            <p><big>↘️ <strong>Subscribe</strong> by copying the URL into your RSS reader.</big></p>
          </div>
          <div class="head">
            <div class="avatar">
              <img src="/images/IMG_0002.jpg" height="100" width="100" alt="Avatar of Daniel Andrlik"/>
            </div>
            <div class="description">
              <p><xsl:value-of select="atom:feed/atom:subtitle"/></p>
              <p><a hreflang="en"><xsl:attribute name="href"><xsl:value-of select="atom:feed/atom:link/attribute::href"/></xsl:attribute>Visit Website &#x2192;</a></p>
            </div>
          </div>
        </header>
        <main>
        <p class="about"><img src="/favicon.png" alt="Ministry of Intrigue icon" width="24" height="24"/><a href="https://www.andrlik.org/about/">About Daniel Andrlik</a>.</p>
          <h2>📄 Recent Posts</h2>
          <xsl:for-each select="atom:feed/atom:entry">
            <article>
              <h3><a hreflang="en"><xsl:attribute name="href"><xsl:value-of select="atom:link/attribute::href"/></xsl:attribute><xsl:value-of select="atom:title"/></a></h3>
              <footer>Published: <time><xsl:value-of select="atom:published" /></time></footer>
            </article>
          </xsl:for-each>
        </main>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

You can also find a gist of both the XLS file and the CSS I’m using here.

The end result is that if someone clicks the link to my Atom feed and their browser doesn’t support immediately subscribing it gives them an easy to read HTML view as opposed to a whole page of XML for robots.

Screenshot showing a fully styled RSS feed
Screenshot of my current feed

There are some limitations to this approach. While the full text of an entry does appear in the feed for a feedreader, you can’t include the content or summary for each post in this listing as it will auto-escape any of the HTML entities in your text, which looks terrible.

I’m sure there’s a much more elegant way of doing this, but as a first attempt, it works well, and I’m happy with it.

Related

Soaring with Pelican
·737 words·4 mins
Articles Personal Development Assorted Geekery Meta Python Pelican
Get Pelican: it’s good! There comes a time in every young man’s life when he begins to neglect his digital lawn, and the weeds grow so thick you wouldn’t think there was any home there at all.
Fun With Django Feeds
·1140 words·6 mins
Articles Atom Development Django Howto Python Rss
I would like to take a moment to show you how easy it is to create feeds in Django, and the easiest way to do that is by example. For brevity’s sake, I’m going to use a simplified version of a Django-powered blog.
Weeknotes for 2022-03-18
·499 words·3 mins
Weeknotes Personal Python Games Tv Movies Fish Pyenv Asdf Zsh Ohmyzsh Explorers Wanted Pelican
This week in the world of Daniel: I updated my quote service project to use my reusable django-quotes app. This should make maintenance easier. Since this is a backwards-incompatible change, I also updated ewdiscordbot and ewtwitterbot to use the new API endpoints.