Styling an RSS Feed

We’re going to briefly dip our toes into XSLT. We’ll be linking to an XSLT file from your syndicated feed for a better presentation when visitors view it on your website. This won’t change how most feed readers display the information from your feed.

What is XSLT?

If you understand the relationship between HTML and CSS then you’ll understand the relationship between XML and XSLT.

Without any style information an XML file will display as a simple document tree. If anyone accidentally lands on this file, it might cause them some confusion. To the average visitor this looks incomprehensible.

An unstyled XML file

XSLT is the recommended style sheet language for XML. You can use XSLT to transform the XML data into other types of documents, anything from simple text files to desktop publishing PostScript documents. For our purposes we’ll be transforming the XML from our RSS feed into HTML. The XSLT file will basically transform an unstyled feed itno something a little more useful and explanatory for visitors who haven’t seen a feed before.

An XML file transformed with XSLT into an HTML file

By transforming your XML document into HTML you make a much better presentation and experience for your visitors, and it can also more accessible than plain XML.

Learning XSLT

Write the XSLT file using an .xsl extension.

If you include HTML elements within some of your XML elements (title, description, etc.) you may have to include an attribute to get the HTML to display properly: disable-output-escaping="yes".

Understanding XPath

XSLT uses XPath to locate the various nodes in an XML file. The syntax is identical to the paths that you’re already using to point to files in HTML URLs. For example, the path in the select attribute...

select="/rss/channel/title"

...corresponds to nodes in the XML document:

<rss version="2.0">
  <channel>
    <title>HTML Hobbyist News</title>
    ...

By using XPath we can address the data values in our XML file for them to be transformed.

Once we’ve found the node we’re looking for we can pull out the data from its attributes with @ followed by the name of the attribute.

select="/rss/channel/atom:link/@href"

This is the basic way that various elements in XSLT retrieve the data from the XML file.

XSLT Elements

XML Declaration

<?xml version="1.0" encoding="utf-8"?>

Just like in our RSS file, the XML declaration gives applications instructions on how to parse the XML file. The version designates what version of XML to use, and encoding designates what type of encoding is used in the file.

<xsl:stylesheet>

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

The <xsl:stylesheet> element is the root element of an XSLT file. Just like <html> is the root for HTML and <rss> for RSS feeds.

<xsl:output>

<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>

We’re using this node to declare that we’re transforming the document into HTML.

<xsl:template>

<xsl:template match="/">

The <xsl:template> element defines a template. The match="/" attribute associates the template with the root of the XML source document. We don’t need to make it more complicated than that.

<xsl:text>

<xsl:text>Text goes here</xsl:text>

Presents text. It’s often used to preserve whitespace around text when it wouldn’t ordinarily be preserved.

<xsl:value-of>

<xsl:value-of select="/rss/channel/title"/>

Finds the value of the node referenced in the select attribute’s path and adds that value into the transformation. In this case it looks for the rss element, then the channel element inside of that, then the title element inside of that, and return the value of that element in the XML file.

<xsl:attribute>

<a>
  <xsl:attribute name="href">
    https://www.htmlhobbyist.com/
  </xsl:attribute>
</a>

Adds the value of the xsl:attribute element, and replaces the named attribute in the parent element with the value of the node.

<xsl:if>

<xsl:if test="source != ''">
  <p>Source: 
    <a target="_blank" rel="noopener noreferrer">
      <xsl:attribute name="href"><xsl:value-of select="source/@url"/></xsl:attribute>
      <xsl:value-of select="source"/>
    </a>
  </p>
</xsl:if>

In this case we’re using the if statement to test if there’s a value in the source element, and if there is, we’re displaying that information.

<xsl:for-each>

<xsl:for-each select="/rss/channel/item">
  <article>
    <h3>
      <a target="_blank" rel="noopener noreferrer">
        <xsl:attribute name="href">
          <xsl:value-of select="link"/>
        </xsl:attribute>
        <xsl:value-of select="title"/>
      </a>
    </h3>
    <p><xsl:value-of select="description"/></p>
    <footer>
      Joined:
      <time>
        <xsl:value-of select="pubDate" />
      </time>
    </footer>
  </article>
</xsl:for-each>

The <xsl:for-each> allows you to loop through a series of identical elements. For each item in the channel we want to display it in a certain way. If there are nine items in my channel element, then the block of code in the <xsl:for-each> will display nine times.

<xsl:for-each> and <xsl:if>

I don’t want to go too far into programming, if statements and for loops, but this is too useful not to share:

<xsl:for-each select="/rss/channel/category">
  <xsl:value-of select="."/>
  <xsl:if test="position()!=last()">
    <xsl:text>, </xsl:text>
  </xsl:if>
  <xsl:if test="position()=last()-1">
    <xsl:text> and </xsl:text>
  </xsl:if>
  <xsl:if test="position()=last()">
    <xsl:text>.</xsl:text>
  </xsl:if>
</xsl:for-each>

Loop through every category element in the channel, and add punctuation depending on the position. That would return a comma separated list of your categories: News, Updates, and Announcements.

HTML Template

Use valid HTML, and intersperse your XML throughout the template.

XSLT code for feed.xsl
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
  <html lang="en">
    <head>
      <title><xsl:value-of select="/rss/channel/title"/> RSS Feed</title>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1" />
      <link rel="stylesheet" type="text/css" href="https://www.htmlhobbyist.com/feeds/feed.css" media="screen" />
      <link rel="icon" href="/favicon.ico" sizes="any" />
      <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
    </head>
    <body>
      <header>
        <a target="_blank" rel="noopener noreferrer">
          <xsl:attribute name="href">
            <xsl:value-of select="/rss/channel/link"/>
          </xsl:attribute>
          <img src="/html-hobbyist-square-animated-opt.svg" height="160" width="200" alt="The HTML Hobbyist" />
        </a>
        <h1><xsl:value-of select="/rss/channel/title"/> RSS Feed</h1>
        <p>
          <xsl:value-of select="/rss/channel/description"/>
        </p>
        <a target="_blank" rel="noopener noreferrer">
          <xsl:attribute name="href">
            <xsl:value-of select="/rss/channel/link"/>
          </xsl:attribute>
          www.htmlhobbyist.com
        </a>
      </header>
      <main>
        <h2>Members</h2>
        <xsl:for-each select="/rss/channel/item">
          <article>
            <h3>
              <a target="_blank" rel="noopener noreferrer">
                <xsl:attribute name="href">
                  <xsl:value-of select="link"/>
                </xsl:attribute>
                <xsl:value-of select="title"/>
              </a>
            </h3>
            <p><xsl:value-of select="description"/></p>
            <footer>
              Joined:
              <time>
                <xsl:value-of select="pubDate" />
              </time>
            </footer>
          </article>
        </xsl:for-each>
      </main>
      <footer>
        <nav>
          <a target="_blank" rel="noopener noreferrer">
            <xsl:attribute name="href">
              <xsl:value-of select="/rss/channel/link"/>/feeds/
            </xsl:attribute>Feeds
          </a>
          <a target="_blank" rel="noopener noreferrer">
            <xsl:attribute name="href">
              <xsl:value-of select="/rss/channel/link"/>/about/
            </xsl:attribute>About
          </a>
          <a target="_blank" rel="noopener noreferrer">
            <xsl:attribute name="href">
              <xsl:value-of select="/rss/channel/link"/>/contact/
            </xsl:attribute>Contact
          </a>
          <a target="_blank" rel="noopener noreferrer">
            <xsl:attribute name="href">
              <xsl:value-of select="/rss/channel/link"/>/announcements/
            </xsl:attribute>Announcements
          </a>
        </nav>
        <p>&#x00A9;2022-2024 N.E.Lilly</p>
      </footer>
    </body>
  </html>
</xsl:template>
</xsl:stylesheet>
            

Applying the XLST

Just like linking HTML to a CSS file, you need to link your XML file to your XSLT file.

<?xml-stylesheet href="https://www.htmlhobbyist.com/feeds/feed.xsl" type="text/xsl"?>

Add the XSLT link to the top of your XML file after the XML declaration.

<?xml version="1.0" encoding="UTF-8" ?>
  <?xml-stylesheet href="https://www.htmlhobbyist.com/feeds/feed.xsl" type="text/xsl"?>
  <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
...
feed.xml with the XSLT added

What’s great about using an XSLT file is that all of your feeds can use the same one, providing a uniform look on all your feeds.

Don’t get wrapped up so much in styling the syndicated feed that you forget it’s purpose: sharing your web content as content, and not specifically to be a marvel of design. Sometimes feed reader extensions will override the styles in the browser. That makes sense, if someone is using a feed reader extension they’d probably expect to see the feed reader when viewing a syndicated feed, and not the personal styles that you created. It’s likely that you may be the only person to ever see these styles applied to your feed, so decide for yourself if this work is worth the effort.

This barely scratches the surface of what can be possible with XSLT in general, but should be sufficient to present your syndicated feed in a manner fitting your website and help visitors who find it to subscribe to it.