diff --git a/content/posts/prettify-hugo-rss-feed-xslt/getting-there-feed.png b/content/posts/prettify-hugo-rss-feed-xslt/getting-there-feed.png new file mode 100644 index 0000000..d634594 Binary files /dev/null and b/content/posts/prettify-hugo-rss-feed-xslt/getting-there-feed.png differ diff --git a/content/posts/prettify-hugo-rss-feed-xslt/index.md b/content/posts/prettify-hugo-rss-feed-xslt/index.md new file mode 100644 index 0000000..e7eafc2 --- /dev/null +++ b/content/posts/prettify-hugo-rss-feed-xslt/index.md @@ -0,0 +1,230 @@ +--- +title: "Prettify Hugo RSS Feeds with XSLT" +date: 2024-04-30 +description: "Making my Hugo-generated RSS XML look as good to human visitors as it does to feed readers." +featured: false +thumbnail: pretty-feed.png +toc: true +comments: true +categories: Backstage +tags: + - hugo + - meta +--- +I put in some work several months back making my sure my site's RSS would work well in a feed reader. This meant making a *lot* of modifications to the [default Hugo RSS template](https://github.com/gohugoio/hugo/blob/master/tpl/tplimpl/embedded/templates/_default/rss.xml). I made it load the full article text rather than just the summary, present correctly-formatted code blocks with no loss of important whitespace, include inline images, and even pass online validation checks: + +[![Validate my RSS feed](valid-rss-rogers.png)](http://validator.w3.org/feed/check.cgi?url=https%3A//runtimeterror.dev/feed.xml) + +But while the feed looks great when rendered by a reader, the browser presentation left some to be desired... + +![Ugly RSS rendered without styling](ugly-rss.png) + +It feels like there should be a friendlier way to present a feed "landing page" to help users new to RSS figure out what they need to do in order to follow a blog - and there absolutely is. In much the same way that you can prettify plain HTML with the inclusion of a CSS stylesheet, you can also style boring XML using [eXtensible Stylesheet Language Transformations (XSLT)](https://www.w3schools.com/xml/xsl_intro.asp). + +This post will quickly cover how I used XSLT to style my blog's RSS feed and made it look like this: + +![Much more attractive RSS feed with styling to fit the site's theme](pretty-feed.png) + +### Starting Point +The [RSS Templates](https://gohugo.io/templates/rss/) page from the Hugo documentation site provides some basic information about how to generate (and customize) an RSS feed for a Hugo-powered site. The basic steps are to [enable the RSS output in `hugo.toml`](https://github.com/jbowdre/runtimeterror/blob/871be9794234177c1bfa0b1c470873bde8f046be/config/_default/hugo.toml#L19-L30), include a link to the generated feed inside the `` element of the site template (I added it to [`layouts/partials/head.html`](https://github.com/jbowdre/runtimeterror/blob/871be9794234177c1bfa0b1c470873bde8f046be/layouts/partials/head.html#L8-L11)), and (optionally) include a customized RSS template to influence how the output gets rendered. + +Here's the content of my `layouts/_default/rss.xml`, before adding the XSLT styling: + +```xml +# torchlight! {"lineNumbers": true} +{{- $pctx := . -}} +{{- if .IsHome -}}{{ $pctx = .Site }}{{- end -}} +{{- $pages := slice -}} +{{- if or $.IsHome $.IsSection -}} +{{- $pages = (where $pctx.RegularPages "Type" "in" site.Params.mainSections) -}} +{{- else -}} +{{- $pages = (where $pctx.Pages "Type" "in" site.Params.mainSections) -}} +{{- end -}} +{{- $limit := .Site.Config.Services.RSS.Limit -}} +{{- if ge $limit 1 -}} +{{- $pages = $pages | first $limit -}} +{{- end -}} +{{- printf "" | safeHTML }} + + + {{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }} + {{ .Permalink }} + Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }} + Hugo -- gohugo.io{{ with .Site.LanguageCode }} + {{.}}{{end}}{{ with .Site.Copyright }} + {{.}}{{end}}{{ if not .Date.IsZero }} + {{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}{{ end }} + {{- with .OutputFormats.Get "RSS" -}} + {{ printf "" .Permalink .MediaType | safeHTML }} + {{- end -}} + + {{ .Site.Params.fallBackOgImage | absURL }} + {{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }} + {{ .Permalink }} + + {{ range $pages }} + + {{ .Title | plainify }} + {{ .Permalink }} + {{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }} + {{ with .Site.Params.Author.name }}{{.}}{{ end }} + {{ with .Params.series }}{{ . | lower }}{{ end }} + {{ range (.GetTerms "tags") }} + {{ .LinkTitle }}{{ end }} + {{ .Permalink }} + {{- $content := replaceRE "a href=\"(#.*?)\"" (printf "%s%s%s" "a href=\"" .Permalink "$1\"") .Content -}} + {{- $content = replaceRE "img src=\"(.*?)\"" (printf "%s%s%s" "img src=\"" .Permalink "$1\"") $content -}} + {{- $content = replaceRE "" "" $content -}} + {{- $content = replaceRE `-moz-tab-size:\d;-o-tab-size:\d;tab-size:\d;?` "" $content -}} + {{ $content | html }} + + {{ end }} + + +``` + +There's a lot going on here, but much of it is conditional logic so that Hugo can use the same template to render feeds for individual tags, categories, or the entire site. It then loops through the `range` of pages of that type to generate the data for each post. It also uses the [Hugo `strings.ReplaceRE` function](https://gohugo.io/functions/strings/replacere/) to replace relative image and anchor links with the full paths so those references will work correctly in readers, and to clean up some potentially-problematic HTML markup that was causing validation failures. + +All I really need to do to get this XML ready to be styled is just link in a style sheet, and I'll do that by inserting a `` element directly below the top-level `` one: + +```xml +# torchlight! {"lineNumbers": true, "lineNumbersStart": 10} +{{- if ge $limit 1 -}} +{{- $pages = $pages | first $limit -}} +{{- end -}} +{{- printf "" | safeHTML }} +{{ printf "" | safeHTML }} + +``` + +I'll put the stylesheet in `static/xml/feed.xsl` so Hugo will make it available at the appropriate path. + +### Creating the Style +While trying to figure out how I could dress up my RSS XML, I came across the [default XSL file](https://github.com/getnikola/nikola/blob/master/nikola/data/themes/base/assets/xml/rss.xsl) provided with the [Nikola SSG](https://getnikola.com/), and I thought it looked like a pretty good starting point. + +Here's Nikola's default XSL: + +```xml +# torchlight! {"lineNumbers": true} + + + + + + + + +<xsl:value-of select="rss/channel/title"/> (RSS) + + + +

(RSS)

+

This is an RSS feed. To subscribe to it, copy its address and paste it when your feed reader asks for it. It will be updated periodically in your reader. New to feeds? Learn more.

+

+ +urladdressfalse +

+

Preview of the feed’s current headlines:

+
    + +
  1. +
    +
+ + +
+
+``` + +If I just plug that in at `static/xml/feed.xml`, I do successfully get a styled (though *very* white) RSS page: + +![A very bright white (but styled) RSS page](very-white-feed.png) + +I'd like this to inherit the same styling as the rest of the site so that it looks like it belongs. I can go a long way toward that by bringing in the CSS stylesheets that are used on every page, and I'll also tweak the existing `<xsl:value-of select="rss/channel/title"/> (RSS) + + + + + +``` + +While I'm at it, I'll also go on and add in some favicons: + +```xml +# torchlight! {"lineNumbers": true, "lineNumbersStart": 10} +<xsl:value-of select="rss/channel/title"/> (RSS) + + + + + + + + + + +``` + +That's getting there: + +![A darker styled RSS page](getting-there-feed.png) + +Including those CSS styles means that the rendered page now uses my color palette and the [font I worked so hard to integrate](/using-custom-font-hugo/). I'm just going to make a few more tweaks to change some of the formatting, put the `New to feeds?` bit on its own line, and point to my [self-hosted instance of the SearXNG metasearch engine](https://scribbles.jbowdre.lol/post/self-hosting-a-search-engine-iyjdlk6y) instead of DDG. + +Here's my final (for now) `static/xml/feed.xsl` file: + +```xml +# torchlight! {"lineNumbers": true} + + + + + + + + + +<xsl:value-of select="rss/channel/title"/> (RSS) + + + + + + + + + + + + +

(RSS)

+

This is an RSS feed. To subscribe to it, copy its address and paste it when your feed reader asks for it. It will be updated periodically in your reader.

+

New to feeds? Learn more.

+

+ +urladdressfalse +

+

Recent posts:

+
    + +
  • +
    +
+ + +
+
+``` + +I'm pretty pleased with [that result](/feed.xml)! \ No newline at end of file diff --git a/content/posts/prettify-hugo-rss-feed-xslt/pretty-feed.png b/content/posts/prettify-hugo-rss-feed-xslt/pretty-feed.png new file mode 100644 index 0000000..2f3039b Binary files /dev/null and b/content/posts/prettify-hugo-rss-feed-xslt/pretty-feed.png differ diff --git a/content/posts/prettify-hugo-rss-feed-xslt/ugly-rss.png b/content/posts/prettify-hugo-rss-feed-xslt/ugly-rss.png new file mode 100644 index 0000000..19eece2 Binary files /dev/null and b/content/posts/prettify-hugo-rss-feed-xslt/ugly-rss.png differ diff --git a/content/posts/prettify-hugo-rss-feed-xslt/valid-rss-rogers.png b/content/posts/prettify-hugo-rss-feed-xslt/valid-rss-rogers.png new file mode 100644 index 0000000..c64445a Binary files /dev/null and b/content/posts/prettify-hugo-rss-feed-xslt/valid-rss-rogers.png differ diff --git a/content/posts/prettify-hugo-rss-feed-xslt/very-white-feed.png b/content/posts/prettify-hugo-rss-feed-xslt/very-white-feed.png new file mode 100644 index 0000000..c366e31 Binary files /dev/null and b/content/posts/prettify-hugo-rss-feed-xslt/very-white-feed.png differ