RSS 中的文章预览Article previews in RSS
作者长期在 RSS 订阅中仅提供标题和日期,拒绝加入全文或预览内容,认为技术上难以实现。经过三年考虑后,他决定采用 Emacs Lisp 脚本在生成 RSS 时自动提取前几段作为摘要,实现了轻量级文章预览功能。该方案无需外部服务,完全基于本地构建流程,保持了 RSS 的简洁性与可读性。
kqr
大约三年前,这个网站的 RSS 订阅源内容一直非常贫乏。它只包含文章标题和日期,别无其他。许多读者曾请求我加入全文或至少是预览内容,但我一直拖延,因为从技术上实现听起来相当困难。
本网站 RSS 订阅源的生成方式分为两步:
在整个过程中,文章内容并未参与其中!订阅源完全基于文件元数据构建。每当我考虑添加内容时,都会被问题的复杂性所困扰:
并非说我无法解决所有这些难题,只是不愿为此投入时间,因为这需要耗费大量精力。
昨天我决定简化问题:不包含内部链接、不执行代码、不显示图片等。只需提取文章的前四个基本元素,如段落、表格和列表。通过类似以下方式,从文件路径获取这些内容是可行的。
In[1]:
(defun tw-get-teaser-contents (source-path)
"Get org-element data of early parts of SOURCE-PATH."
(with-temp-buffer
;; Load file contents into Org mode.
(insert-file-contents source-path)
(org-mode)
;; Take the first four elements ...
(seq-take
;; ... from a subset of the full parse sequence.
(org-element-map (org-element-parse-buffer)
'(paragraph src-block quote-block plain-list)
#'identity
nil nil '(quote-block plain-list))
4)))接着,我们必须去除其中的难点(内部链接)、性能问题(代码块执行)和非必要内容(旁注)。虽然存在更好的方法,但我发现了一种迂回的方式来实现。首先,我为 HTML 的一个有限子集创建了一个导出后端。我实际运行的代码稍微复杂一些,以支持我对小型大写字母等的过度使用。但原理是成立的。
In[2]:
;; Custom backend for lo-fi RSS-embedded HTML.
(org-export-define-derived-backend 'teaser-html 'html
:translate-alist
'((link . (lambda (_ contents _) (or contents ""))
(footnote-reference . (lambda (_ _ _) "")))))这可用于生成对 RSS 友好的摘要内容渲染。
In[3]:
(defun tw-get-teaser-html (path)
"Get HTML representing teaser for Org file at PATH."
;; Don't syntax highlight code in RSS.
(let ((org-html-htmlize-output-type nil))
;; Practically unbind function to prevent code block
;; evaluation without disrupting other babel processing.
;;
;; This uses cl-letf rather than advice in order to
;; revert the change when control leaves this function.
(cl-letf (((symbol-function 'org-babel-execute-src-block)
(lambda (&rest _) "")))
(org-export-string-as
;; Render data back as Org source markup.
(mapconcat #'org-element-interpret-data
(tw-get-teaser-contents path)
"\n\n")
;; Then export that as limited HTML.
'teaser-html t '(:with-footnotes nil)))))现在我们有低 fidelity 的 HTML,但由于 RSS 文件的源码是 org-element 语法树,因此必须将文章预览作为 HTML 导出块嵌入到语法树中。
In[4]:
(defun tw-feed-summary (path)
(org-element-create 'export-block
(list :type "HTML"
:value (tw-get-teaser-html path))))这会生成一个 org-element 节点,随后可插入构成 RSS 订阅源的 Org 文档中。
即便如此,仍有一个问题待解决。ox-rss 导出后端会安装一个输出过滤器,通过调用 indent-region 对整个缓冲区进行处理等方式美化 RSS 文件的 XML 格式。要找出某个函数被调用的位置,可以运行 (debug-on-entry 'indent-region),然后查看堆栈跟踪。在调试器中输入 c 继续执行。之后,通过 cancel-debug-on-entry 可停止重复进入调试器。然而,这种方法不会尊重 CDATA 块中的缩进——尤其是对缩进要求严格的代码块!似乎无法配置此行为,但我们可以覆盖执行此操作的功能,使其直接不做任何事。
In[5]:
(advice-add 'org-rss-final-function :override
(lambda (contents _ _) contents))搞定!我认为就是这样了。虽然远非简单(毕竟花了几个小时才搞定),但比我预想的要容易些。
但更糟糕的是,这段代码简直烂透了。我确信肯定有优雅的实现方式,但我写的显然不是。想想看:这段代码先解析一个 Org 文档,保留部分内容,再将其渲染回 Org 格式,导出为 HTML,嵌入另一个 Org 文件,最后再次导出成 HTML 衍生格式——来来回回,反反复复。
当有人让我分享用来修补 Org 以构建这个网站的代码时,我总是建议他们直接去读 Emacs 手册和 Org 的源码,因为里面充斥着这种垃圾代码。我对这些细节其实了解不深,只能靠临时拼凑,结果自然很糟。我可不想让别人觉得能从中学到什么好东西!
需要完整排版与评论请前往来源站点阅读。