[解决] 如何将服务器返回 HTML 内容在 Emacs 易于阅读地渲染

背景

自己习惯使用 Emacs 阅读英语文章。虽然 Emacer 创造了很多的翻译插件,但是很多是在线翻译或者是基础的单词翻译功能。

而我发现 GoldenDict 可以加载很多字典,比如说 《英语常用词疑难用法手册_文字版》,《英文字用法指南 2021 圣诞版》,《idiom大合集3》。这些词典不是单纯的单词翻译,它们可以详细介绍一个单词或者俚语的用法。

但是很不爽的是,我在 Emacs 阅读,发现一个单词,短语的用法想要学习,就需要打开 GoldenDict,这样来回切换软件,让我很没有沉浸感。

我的设想是能不能让 GoldenDict 的内容在 Emacs 上显示

(懒猫大师有个 StarDict 的包可以实现,但是 StarDict 的缺点是很多字典它没有 GoldenDict 丰富,就是我上面举例的都没有)

我无意发现 https://github.com/Crissium/SilverDict 这个项目,作者现实了 GoldenDict 的 Web-based 版本,也就是可以在本地跑服务器,通过 API 进行查询。

我在本地跑起来了,并且通过测试可以得到查询内容:

curl http://127.0.0.1:2628/api/query/Default%20Group/stamina

返回的 HTML 内容:

<!DOCTYPE html>
<html>
<head>
	<title>Definition</title>
	<meta charset="utf-8">
	<style type="text/css">
		.article-block {
			border-top: 2px solid #ccc;
			border-bottom: 2px solid #ccc;
			margin-top: 10px;
			margin-bottom: 10px;
		}

		img {
			max-width: 100%;
		}

		hr {
			border: none;
			border-top: 0.5px solid #ccc;
			width: 98%;
		}

		.dictionary-headings {
			padding-top: 5px;
			padding-bottom: 5px;
			color: darkgreen;
			font-weight: bolder;
		}
	</style>
</head>

<body>

<div class="article-block">
	<h2 class="dictionary-headings" id="不择手段背单词"> <!-- dictionary name -->
		不择手段背单词 <!-- dictionary display name -->
	</h2>
	<font color=red>stamina</font><br><font face="Kingsoft Phonetic Plain, Tahoma" color=#FF6600>5stAminE</font><br><font color=olive>n.(防止疾病、疲劳或经受磨难的)体力,耐力=endurance</font><br><font color=#333333>【记】stam 荷兰著名后卫斯塔姆,以耐力和结巴出众闻名。</font><br><font color=fuchsia>【反】frailty(n.虚弱)</font><br><font color=red>stamina</font><br><font face="Kingsoft Phonetic Plain, Tahoma" color=#FF6600>5stAmInE</font><br><font color=olive>n.体力,耐力</font><br><font color=#993300>【英】n.体力,耐力(staying power, endurance)</font>
 <!-- body of article, already HTML-formatted -->
</div>

<div class="article-block">
	<h2 class="dictionary-headings" id="要你命3000"> <!-- dictionary name -->
		要你命3000 <!-- dictionary display name -->
	</h2>
	<p><font size="4" color="blue" style="font-weight:bold;">【考法①】</font><br> n.耐力: physical or moral strength to resist or withstand illness, fatigue, or hardship
<br><font color="#BEBEBE" style="font-weight:bold;">【例】</font> A marathon challenges a runner's stamina. 马拉松挑战参赛者的耐力。
<br><font color="green" style="font-weight:bold;">【近】</font> endurance, tolerance
<br><font color="#EA0000" style="font-weight:bold;">【反】</font> frailty 脆弱
<br>
 <!-- body of article, already HTML-formatted -->
</div>

</body>

</html>

问题

在 Emacs 得到上述的 HTML 返回内容后,如何进行渲染?

我知道懒猫大神的 Popweb 插件喂入 HTML 就可以显示,但是我是希望像普通的 Emacs buffer 一样,这样我可以在该 buffer 上复制,选择我想要的内容导入 Anki 进行复习。

1 个赞

直接用 Emacs 内置的 eww 试试?

使用 EWW 有个问题,就是我说了使用 Emacs 阅读文章用的是 EAF browser,并使用命令 insert_or_render_by_eww 通过 Readability 将正文提取在 EWW 阅读。

如果使用 EWW 对查询 GoldenDict 返回的 HTML 内容渲染,那么我原来正在使用 EWW 看的文章就没有了。查完单词用法,需要重新打开文章,这样太繁琐了。

EDIT:用eww应该更简单,直接 (eww <url> t)应该不会覆盖你原来用eww打开的文章吧?(会在一个新的buffer打开这个<url>)你可以试试看

我写过一个插件,有段代码解决类似你这个需求的,你看看有没有用

参数buffer里是获取到的html数据,解析后使用shr-insert-document插入到另一个buffer里,然后再看怎么展示这第二个buffer就可以了。

(defun haici--render-html (&optional buffer)
  (or (fboundp 'libxml-parse-html-region)
      (error "This function requires Emacs to be compiled with libxml2"))
  (let (dom
        (begin (point-min)))
    (with-current-buffer (or buffer (current-buffer))
      (setq dom (libxml-parse-html-region begin)))
    (if dom
        (with-current-buffer (get-buffer-create haici--result-buffer-name)
          (erase-buffer)
          (shr-insert-document dom)
          t)
      (message "Cannot render buffer as html."))))
1 个赞

eww 不能搞多个 buffer 么?我没用过 eww 还真不知道这个行不行

我也不清楚, :sweat_smile:

我看了 eaf-browser.el 的

(defun eaf--browser-render-by-eww (url filepath)
  (eww-open-file filepath)

  (setq-local header-line-format (format "EAF Browser: %s" url))

  ;; Try olivetti mode.
  (let ((window-width (/ (eaf--browser-get-window-width) (window-font-width))))
    (when (featurep 'olivetti)
      (olivetti-mode 1)
      (olivetti-set-width (floor (* window-width 0.618))))))

发现主要是通过 (eww-open-file filepath) 来渲染 HTML,所以觉得原来的内容会被覆盖。

谢谢,让我有点头目了。

我知道racket-mode是这样转换的 https://github.com/greghendershott/racket-mode/blob/master/racket-describe.el,

我觉得也可以试试pandoc转org来显示

可以试试和 posframe 一起用:

Peek 2023-11-05 15-00

(defvar silverdict--frame nil)

(defun eli/silverdict-pop-posframe-toggle (word)
  "Toggle silverdict in child frame."
  (interactive "sInput a word: ")
  (unless (and silverdict--frame
               (frame-live-p silverdict--frame))
    (let ((width  (max 100 (round (* (frame-width) 0.62))))
          (height (round (* (frame-height) 0.62))))
      (eli/silverdict--query word)
      (setq silverdict--frame
            (posframe-show
             "*silverdict*"
             :poshandler #'posframe-poshandler-frame-center
             :hidehandler nil
             :left-fringe 8
             :right-fringe 8
             :width width
             :height height
             :min-width width
             :min-height height
             :border-width 2
             :border-color "light gray"
             :background-color (face-background 'tooltip nil t)
             :override-parameters '((cursor-type . t))
             :accept-focus t))

      (with-current-buffer "*silverdict*"
        (setq-local cursor-type 'box)
        (read-only-mode)
        (keymap-local-set "s-p" #'posframe-hide-all))))
  (eli/silverdict--query word)
  (select-frame-set-input-focus silverdict--frame))

(defun eli/silverdict--query (word)
  (let ((buffer (url-retrieve-synchronously
                 (url-encode-url
                  (concat
                   "http://127.0.0.1:2628/api/query/"
                   "Default Group"
                   "/"
                   word))
                 t))
        (inhibit-read-only t)
        dom)
    (with-current-buffer buffer
      (goto-char (point-min))
      (search-forward-regexp "^$")
      (setq dom (libxml-parse-html-region (point))))
    (with-current-buffer (get-buffer-create "*silverdict*")
      (erase-buffer)
      (shr-insert-document dom))))
2 个赞

我看你的示例中没有 posframe?

谢谢,这个包太复杂了,我目前模仿不来。

1 个赞

抱歉,复制的时候漏了,已添加。

优秀得过分! 我想问, 咋能用 emacs 看 economist print edition 的? 我的意思是, 你是下载到纯文本版本的 economist 么? 我就只能下载到 epub 或者 pdf 的

我用 elfeed 看的,RSS: https://feedx.net/rss/economistp.xml

下载的话可以用 calibre + 定时任务,每周自动下任意格式的 TE

1 个赞

感谢感谢! 新世界的大门

我在 The Economist 网站上看文章,如果使用 EAF browser,正常是这样的:

文章的后大部分会被充值广告遮蔽。

我的猜想网站是通过 JS 在内容加载完成后,再遮蔽的。

所以我往往是通过在跳转到文章页面的还没出现遮蔽广告的前一小段时间内,按下快捷键 N eaf-py-proxy-insert_or_render_by_eww,EAF browser 会通过 Readability 抽取出正文,并在 EWW 渲染。

我用利用这用方法,看 SCMP,The Economist 等需要付费订阅的网站往往无往不利,都可以顺利阅读。

2 个赞

你可能在找 shr-render-buffer.

Signature

(shr-render-buffer BUFFER)

Documentation

Display the HTML rendering of the current buffer.

如果已经得到了HTML内容, 只是需要渲染, 可能不是很需要去麻烦eww.

而且渲染后得到的 buffer 是 fundamental-mode, 做各种编辑之类也比较方便.

可以试试:GitHub - ginqi7/html2org: Convert Html to Org mode format

可以把 html 转换为 org mode 展示。

可以 (html2org-fetch-url URL) 发送 request 请求,获取html 然后转换为org mode

可以 (html2org) 把当前 buffer 的 html 转换 为 org mode

你这边的 html 会转换为:

1 个赞