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: j...@example.com
,#+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: j...@example.com
,#+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

Reply via email to