Dear All,
I've got a little static website that gets built with the Org publishing
system. Recently I've been looking at ways to add a full-text RSS feed
with ox-rss.el[0].
Here's a minimum example.
Dependencies:
- Emacs
- Emacs Htmlize
- Emacs Org
- Emacs ox-rss.el
- Make
Incidentally, if you're on a Guix system, I've been using this manifest:
#+begin_src scheme
(specifications->manifest
'("coreutils"
"emacs"
"emacs-htmlize"
"emacs-org"
"emacs-ox-rss"
"make"))
#+end_src
File structure:
#+begin_src shell
$ tree
.
├── Makefile
├── posts
│ ├── another-post
│ │ └── index.org
│ └── a-post
│ └── index.org
└── publish.el
#+end_src
Files:
#+begin_src org :tangle posts/a-post/index.org
,#+title: A post
,#+author: Jane Doe
,#+email: [email protected]
,#+date: <2025-03-23 Sun>
,* Intro
Foo and bar.
- One
- Two
- Three
,* A section
Some data:
| Name | Phone |
|-------+-------|
| Alice | 0123 |
| Bob | 1234 |
| Carol | 2345 |
#+end_src
#+begin_src org :tangle posts/another-post/index.org
,#+title: Another post
,#+author: John Donne
,#+email: [email protected]
,#+date: <2025-02-23 Sun>
,* Intro
Foo and bar.
- One
- Two
- Three
,* Another section
Some code:
,#+begin_src emacs-lisp
(defun my/rss-feed-generate (title list)
"Generate the RSS feed as a string."
(concat "#+TITLE: " title "\n\n"
(org-list-to-subtree list)))
,#+end_src
#+end_src
#+begin_src emacs-lisp :tangle publish.el
(package-initialize)
(require 'ox-publish)
(require 'ox-rss)
(load "ox-rss.el")
(defun my/rss-feed-generate (title list)
"Generate the RSS feed as a string."
(concat "#+TITLE: " title "\n\n"
(org-list-to-subtree list)))
(defun my/rss-feed-format-entry (entry style project)
"Format ENTRY for the RSS feed."
(cond ((not (directory-name-p entry))
(let ((file (org-publish--expand-file-name entry project))
(title (org-publish-find-title entry project)))
(with-temp-buffer
(insert (format "%s\n\n" title))
(insert-file-contents file)
(buffer-string))))
((eq style 'tree)
(file-name-nondirectory (directory-file-name entry)))
(t entry)))
(defun my/rss-feed-publish (plist filename pub-dir)
"Publish the RSS feed."
(when (equal "rss.org" (file-name-nondirectory filename))
(org-rss-publish-to-rss plist filename pub-dir)))
(setq org-publish-project-alist
'(("rss"
:base-directory "posts"
:publishing-directory "build"
:base-extension "org"
:recursive t
:publishing-function my/rss-feed-publish
:rss-extension "xml"
:section-numbers nil
:table-of-contents nil
:html-link-home "https://example.com"
:html-link-use-abs-url t
:html-link-org-files-as-html t
:auto-sitemap t
:sitemap-title "Test"
:sitemap-filename "rss.org"
:sitemap-function my/rss-feed-generate
:sitemap-style tree
:sitemap-sort-files anti-chronologically
:sitemap-format-entry my/rss-feed-format-entry)
("all"
:components ("rss"))))
#+end_src
#+begin_src makefile :tangle Makefile
all: clean build
build:
emacs \
--batch \
--no-init-file \
--load publish.el \
--eval '(org-publish-all t)' \
--kill
clean:
rm -fr build
rm -fr posts/rss.org
rm -fr posts/rss.org~
.PHONY: all build clean
#+end_src
For simplicity, this minimal example only builds the XML feed and none of the
actual pages. Here's the result:
#+begin_src xml
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
xmlns:georss="http://www.georss.org/georss"
xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
xmlns:media="http://search.yahoo.com/mrss/"><channel>
<title>Test</title>
<atom:link href="https://example.com/rss.xml" rel="self"
type="application/rss+xml" />
<link>https://example.com</link>
<description><![CDATA[]]></description>
<language>en</language>
<pubDate>Mon, 03 Mar 2025 09:52:16 +0000</pubDate>
<lastBuildDate>Mon, 03 Mar 2025 09:52:16 +0000</lastBuildDate>
<generator>Emacs 29.4 Org-mode 9.7.20</generator>
<webMaster>user@laptop (nil)</webMaster>
<image>
<url>https://orgmode.org/img/org-mode-unicorn-logo.png</url>
<title>Test</title>
<link>https://example.com</link>
</image>
<item>
<title>another-post</title>
<link>https://example.com/rss.html#org211204a</link>
<author>user@laptop (nil)</author>
<guid isPermaLink="false">https://example.com/rss.html#org211204a</guid>
<pubDate>Mon, 03 Mar 2025 09:52:00 +0000</pubDate>
<description><![CDATA[<div id="outline-container-orgfc772e2"
class="outline-3">
<h3 id="orgfc772e2">Another post</h3>
<div class="outline-text-3" id="text-orgfc772e2">
</div>
<div id="outline-container-orgc8b6f19" class="outline-4">
<h4 id="orgc8b6f19">Intro</h4>
<div class="outline-text-4" id="text-orgc8b6f19">
<p>
Foo and bar.
</p>
</div>
</div>
<div id="outline-container-org63baba9" class="outline-4">
<h4 id="org63baba9">One</h4>
<div class="outline-text-4" id="text-org63baba9">
</div>
</div>
<div id="outline-container-orgd30bd42" class="outline-4">
<h4 id="orgd30bd42">Two</h4>
<div class="outline-text-4" id="text-orgd30bd42">
</div>
</div>
<div id="outline-container-org3fb09c5" class="outline-4">
<h4 id="org3fb09c5">Three</h4>
<div class="outline-text-4" id="text-org3fb09c5">
</div>
</div>
<div id="outline-container-org9a50939" class="outline-4">
<h4 id="org9a50939">Another section</h4>
<div class="outline-text-4" id="text-org9a50939">
<p>
Some code:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span style="font-weight:
bold;">defun</span> <span style="font-weight:
bold;">my/rss-feed-generate</span> (title list)
<span style="font-style: italic;">"Generate the RSS feed as a
string."</span>
(concat <span style="font-style: italic;">"#+TITLE: "</span> title <span
style="font-style: italic;">"\n\n"</span>
(org-list-to-subtree list)))
</pre>
</div>
</div>
</div>
</div>
]]></description>
</item>
<item>
<title>a-post</title>
<link>https://example.com/rss.html#orga85cbea</link>
<author>user@laptop (nil)</author>
<guid isPermaLink="false">https://example.com/rss.html#orga85cbea</guid>
<pubDate>Mon, 03 Mar 2025 09:52:00 +0000</pubDate>
<description><![CDATA[<div id="outline-container-org03c3b3b"
class="outline-3">
<h3 id="org03c3b3b">A post</h3>
<div class="outline-text-3" id="text-org03c3b3b">
</div>
<div id="outline-container-org7453f96" class="outline-4">
<h4 id="org7453f96">Intro</h4>
<div class="outline-text-4" id="text-org7453f96">
<p>
Foo and bar.
</p>
</div>
</div>
<div id="outline-container-org12bfd39" class="outline-4">
<h4 id="org12bfd39">One</h4>
<div class="outline-text-4" id="text-org12bfd39">
</div>
</div>
<div id="outline-container-org05d0a1e" class="outline-4">
<h4 id="org05d0a1e">Two</h4>
<div class="outline-text-4" id="text-org05d0a1e">
</div>
</div>
<div id="outline-container-org1692eeb" class="outline-4">
<h4 id="org1692eeb">Three</h4>
<div class="outline-text-4" id="text-org1692eeb">
</div>
</div>
<div id="outline-container-org89963e0" class="outline-4">
<h4 id="org89963e0">A section</h4>
<div class="outline-text-4" id="text-org89963e0">
<p>
Some data:
</p>
<table border="2" cellspacing="0" cellpadding="6" rules="groups"
frame="hsides">
<colgroup>
<col class="org-left" />
<col class="org-right" />
</colgroup>
<thead>
<tr>
<th scope="col" class="org-left">Name</th>
<th scope="col" class="org-right">Phone</th>
</tr>
</thead>
<tbody>
<tr>
<td class="org-left">Alice</td>
<td class="org-right">0123</td>
</tr>
<tr>
<td class="org-left">Bob</td>
<td class="org-right">1234</td>
</tr>
<tr>
<td class="org-left">Carol</td>
<td class="org-right">2345</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
]]></description>
</item>
</channel>
</rss>
#+end_src
There's a couple of things that I'm not sure about. My main concern is
around bullet point lists. Lists like this:
#+begin_src org
- One
- Two
- Three
#+end_src
get transformed to:
#+begin_src html
<div id="outline-container-org63baba9" class="outline-4">
<h4 id="org63baba9">One</h4>
<div class="outline-text-4" id="text-org63baba9">
</div>
</div>
<div id="outline-container-orgd30bd42" class="outline-4">
<h4 id="orgd30bd42">Two</h4>
<div class="outline-text-4" id="text-orgd30bd42">
</div>
</div>
<div id="outline-container-org3fb09c5" class="outline-4">
<h4 id="org3fb09c5">Three</h4>
<div class="outline-text-4" id="text-org3fb09c5">
</div>
</div>
#+end_src
whereas I'd expect a HTML UL block.
The problem seems to boil down to ~org-list-to-subtree~ and the way the
original Org files are merged into a single Org sitemap.
For instance, this:
#+begin_src emacs-lisp
(org-list-to-subtree '(unordered ("* Intro\n\n- one\n- two\n")))
#+end_src
returns:
#+begin_src text
,* * Intro
,** one
,** two
#+end_src
Whereas, I'd expect:
#+begin_src text
,** Intro
- one
- two
#+end_src
Anybody knows whether I'm not using ~org-list-to-subtree~ correctly or
if there's a more clever way to define a ~my/rss-feed-generate~
function?
Separately, less important, I wonder why I need to explicitly load
~(load "ox-rss.el")~. Only requiring (without also loading) the library
results in a silent failure, i.e. an XML with no items. I suppose this
may have to do with the lack of an ~autoload~ declaration in the
ox-rss.el library?
Any other suggestion around producing RSS feeds out of a Org-based
website also appreciated.
Have a lovely day!
Best wishes, Fabio.
PS: Should you reply to this email, kindly CC me in.
- 0: https://github.com/BenedictHW/ox-rss
--
Fabio Natali
https://fabionatali.com