- 1 前言
- 2 Org 文件导出为 Markdown 文件时的链接改造
- 2.1 背景
- 2.2 向大模型描述问题
- 2.3 调试和反馈
- 2.4 可能是 Org-mode 的一个 bug
- 2.5 增加后处理解决这个 bug
- 2.6 最终生成的代码
- 2.7 最终的效果
- 3 结语
1 前言 🔗
在大模型时代,一切皆有可能。对于 Emacs 同样如此,大模型拉低学习了 Emacs 的门槛,我们通过大模型可以做到以前我们敢想不敢做,或敢做没时间做,或有时间不会做的事。
今天继续这个新的系列,跟大家分享在大模型时代,我们可以如何“玩” Emacs,我们应该怎么用大模型来满足自己对 Emacs 使用的需求。
2 Org 文件导出为 Markdown 文件时的链接改造 🔗
2.1 背景 🔗
我经常使用 Emacs 的 Org-mode 和 honkit 软件来写产品手册或技术文档。我会先用 Org-mode 来写原始的文档,然后导出为 Markdown 文件,然后在命令行通过 npx honkit serve
命令生成静态站点,打个包把所有的 HTML 发给前端部署一下,整个产品手册就写完了。成品大概长下面这样:
![](/ox-hugo/img_20240715_152235.png)
整个工作流非常顺畅,就是有一点很烦人,我在 Org 文档里会有很多截图,我的截图会带一些属性,例如 width
, class
等,在导出为 Markdown 时,这些属性会丢失。
下面是我在 Org 文件里的截图的文本的一个示例,通过 #+ATTR_HTML:
会添加一些额外的属性:
#+DOWNLOADED: screenshot @ 2024-07-11 四 06:41:14
#+CAPTION:
#+ATTR_ORG: :width 400
#+ATTR_LATEX: :width 0.5\linewidth :float nil
#+ATTR_HTML: :width 400 :class zoomImage
[[file:org文件导出为Markdown文件时的链接改造.assets/demo.png]]
在实际导出为 Markdown 文件后,链接如下:
![img](org文件导出为Markdown文件时的链接改造.assets/demo.png)
可以看到丢失了 width
, class
等属性,这样会导致最终生成网页里的图片的大小会失控,有的时候我明明希望它以 400px 显示,却以 800px 来显示,看上去很不协调,我更希望的是下面这张图:
![](/ox-hugo/img_20240711_154812.png)
![](/ox-hugo/img_20240711_154942.png)
Markdown 天然支持 <img src="" width="" alt="" class="">
这样的标签,因此自然而然的就想,是不是可以让 Org-mode 在导出为 Markdown 格式时,将 Markdown 格式的链接做一个转换,同时带上 width 这些属性。
2.2 向大模型描述问题 🔗
在向大模型描述问题的时候,请注意,最好使用实际的例子,比如实际的 Org 文档里的图片链接,导出为 Markdown 文档之后的链接,并写明期望的结果:
![](/ox-hugo/img_20240715_112632.png)
2.3 调试和反馈 🔗
在拿到大模型给的初版代码时,我们需要进行试运行,并将相应的报错(如有)的信息给到大模型,让大模型调整代码。
![](/ox-hugo/img_20240715_142222.png)
2.4 可能是 Org-mode 的一个 bug 🔗
在调试许久之后,最终得到了如下的代码:
(defun my-org-md-export-image-links (text backend info)
"Convert image file links to HTML img tags with attributes on Markdown export."
(when (org-export-derived-backend-p backend 'md)
(replace-regexp-in-string
"\\!\\[.*\\](\\(.*\\))" ;; 更加严格匹配整个 Markdown 图片链接
(lambda (m)
(let* ((path (match-string 1 m))
;; 从 Org 文件解析中寻找对应的链接元素
(link (org-element-map (org-element-parse-buffer) 'link
(lambda (el)
(when (string= (org-element-property :path el) path)
el))
nil t 'link))
;; 提取链接的父元素中的 HTML 属性
(attributes (and link (org-export-read-attribute :attr_html (org-element-property :parent link))))
(width (plist-get attributes :width))
(class (plist-get attributes :class)))
;; 生成带属性的 HTML <img> 标签
(if attributes ; 只有当找到属性时才替换
(format "<img src=\"%s\" alt=\"%s\" %s %s>"
path
(file-name-nondirectory path)
(if width (format "width=\"%s\"" width) "")
(if class (format "class=\"%s\"" class) ""))
m))) ; 如果没有属性或未找到匹配的元素,则不修改
text)
))
(add-to-list 'org-export-filter-link-functions
'my-org-md-export-image-links)
执行了这段代码,我们发现,生成的新的链接,是带上原有图片的 width 等属性了,但是生成的链接总是多了一些额外的字符串,我自己判断是在链接做正则替换的时候出了问题。
![i<img src="appComponents.assets/img_20231008_094849.png" alt="img" width="300" class="zoomImage">omponents.assets/img_20231008_094849.png)
然而,更有趣的是,即使我使用 emacs -Q
来启动 Emacs,执行上面的代码,依然存在这样的问题。
为了检验正则表达式有无问题,我写了下面的一个小函数来做测试,发现测试结果是符合预期的 <img src=\"appTest.assets/img_20231007_181409.png\" alt=\"img_20231007_181409.png\" 400 zoomImage>
, 并没有出现上面的情况:
(replace-regexp-in-string
"\\!\\[.*?\\](\\([^)]+\\))"
(lambda (m)
(let* ((path (match-string 1 m)))
(if t
(format "<img src=\"%s\" alt=\"%s\" width=\"%s\" class=\"%s\">"
path
(file-name-nondirectory path)
"400"
"zoomImage")
m)))
"BalaBala...:
![img](appTest.assets/img_20231007_181311.png)
Balabala:
![img](appTest.assets/img_20231007_181409.png)")
“BalaBala…:\n\n<img src=\“appTest.assets/img_20231007_181311.png\” alt=\“img_20231007_181311.png\” width=\“400\” class=\“zoomImage\">\n\nBalabala:\n\n<img src=\“appTest.assets/img_20231007_181409.png\” alt=\“img_20231007_181409.png\” width=\“400\” class=\“zoomImage\">”
那么,此时此刻,凭我的知识和代码能力,是无能为力了,只能姑且认为这是 Org-mode 的一个隐藏 bug 吧。
2.5 增加后处理解决这个 bug 🔗
为了解决这个问题,我想到我们能不能在最终生成完的 Markdown 文件里找到所有 ![i<img src="xxx" ...>components.assets/...png)
这样的字符串,将尖括号两边的多余字符串给删掉来解决呢?
于是就有了下面这一次与大模型的对话和尝试:
![](/ox-hugo/img_20240715_155723.png)
最终经过尝试,通过下面这一段代码,在导出为 Markdown 后,在生成的 markdown 文件里处理这些有问题的链接:
(defun fix-markdown-image-links ()
"Fix image links in the exported Markdown file."
(goto-char (point-min))
(while (re-search-forward "!\\[i\\(<img.*?\">\\).*" nil t)
(replace-match "\\1")))
(defun my/org-export-to-markdown (org &rest args)
"Post-process the generated Markdown file."
(let ((output-file (concat (file-name-sans-extension (buffer-file-name)) ".md")))
(when (file-exists-p output-file)
(find-file output-file)
;; 在这里进行你需要的文本处理操作,例如添加特定文本
(fix-markdown-image-links)
;; 保存并关闭文件
(save-buffer)
(kill-buffer))))
(advice-add 'org-md-export-to-markdown :after 'my/org-export-to-markdown)
(advice-add 'org-gfm-export-to-markdown :after 'my/org-export-to-markdown)
完美!
2.6 最终生成的代码 🔗
(defun my-org-md-export-image-links (text backend info)
"Convert image file links to HTML img tags with attributes on Markdown export."
(when (org-export-derived-backend-p backend 'md)
(replace-regexp-in-string
"\\!\\[.*\\](\\(.*\\))" ;; 更加严格匹配整个 Markdown 图片链接
(lambda (m)
(let* ((path (match-string 1 m))
;; 从 Org 文件解析中寻找对应的链接元素
(link (org-element-map (org-element-parse-buffer) 'link
(lambda (el)
(when (string= (org-element-property :path el) path)
el))
nil t 'link))
;; 提取链接的父元素中的 HTML 属性
(attributes (and link (org-export-read-attribute :attr_html (org-element-property :parent link))))
(width (plist-get attributes :width))
(class (plist-get attributes :class)))
;; 生成带属性的 HTML <img> 标签
(if attributes ; 只有当找到属性时才替换
(format "<img src=\"%s\" alt=\"%s\" %s %s>"
path
(file-name-nondirectory path)
(if width (format "width=\"%s\"" width) "")
(if class (format "class=\"%s\"" class) ""))
m))) ; 如果没有属性或未找到匹配的元素,则不修改
text)
))
(add-to-list 'org-export-filter-link-functions
'my-org-md-export-image-links)
(defun fix-markdown-image-links ()
"Fix image links in the exported Markdown file."
(goto-char (point-min))
(while (re-search-forward "!\\[i\\(<img.*?\">\\).*" nil t)
(replace-match "\\1")))
(defun my/org-export-to-markdown (org &rest args)
"Post-process the generated Markdown file."
(let ((output-file (concat (file-name-sans-extension (buffer-file-name)) ".md")))
(when (file-exists-p output-file)
(find-file output-file)
;; 在这里进行你需要的文本处理操作,例如添加特定文本
(fix-markdown-image-links)
;; 保存并关闭文件
(save-buffer)
(kill-buffer))))
(advice-add 'org-md-export-to-markdown :after 'my/org-export-to-markdown)
(advice-add 'org-gfm-export-to-markdown :after 'my/org-export-to-markdown)
2.7 最终的效果 🔗
原始 org 文档:
* title
hello, world!
#+DOWNLOADED: screenshot @ 2024-06-27 四 15:41:48
#+CAPTION:
#+ATTR_ORG: :width 800
#+ATTR_LATEX: :width 1.0\linewidth :float nil
#+ATTR_HTML: :width 800 :class zoomImage
[[file:temp.assets/img_20240627_154148.png]]
Good!
最终生成的 markdown 文档:
- [title](#org79702d9)
<a id="org79702d9"></a>
# title
hello, world!
<img src="temp.assets/img_20240627_154148.png" alt="img_20240627_154148.png" width="800" class="zoomImage">
Good!
3 结语 🔗
今天这个案例,我们无法通过大模型一次性地解决问题,我们可以根据需要,让大模型辅助人,人需要思考,很多时候,人的思路比大模型更重要,人能更好地指导大模型如何服务于人。
后续,将会继续分享几个案例。
- org 文件导出为 HTML 的 css 改造
- …
我也会持续的使用 ChatGPT 和 Emacs,持续分享我的心得,感谢您的阅读。