大模型时代我们怎么玩Emacs:5. Org 文件导出为 Markdown 文件时的链接改造

· 2147字 · 5分钟

1 前言 🔗

在大模型时代,一切皆有可能。对于 Emacs 同样如此,大模型拉低学习了 Emacs 的门槛,我们通过大模型可以做到以前我们敢想不敢做,或敢做没时间做,或有时间不会做的事。

今天继续这个新的系列,跟大家分享在大模型时代,我们可以如何“玩” Emacs,我们应该怎么用大模型来满足自己对 Emacs 使用的需求。

2 Org 文件导出为 Markdown 文件时的链接改造 🔗

2.1 背景 🔗

我经常使用 Emacs 的 Org-mode 和 honkit 软件来写产品手册或技术文档。我会先用 Org-mode 来写原始的文档,然后导出为 Markdown 文件,然后在命令行通过 npx honkit serve 命令生成静态站点,打个包把所有的 HTML 发给前端部署一下,整个产品手册就写完了。成品大概长下面这样:

整个工作流非常顺畅,就是有一点很烦人,我在 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 来显示,看上去很不协调,我更希望的是下面这张图:

Markdown 天然支持 <img src="" width="" alt="" class=""> 这样的标签,因此自然而然的就想,是不是可以让 Org-mode 在导出为 Markdown 格式时,将 Markdown 格式的链接做一个转换,同时带上 width 这些属性。

2.2 向大模型描述问题 🔗

在向大模型描述问题的时候,请注意,最好使用实际的例子,比如实际的 Org 文档里的图片链接,导出为 Markdown 文档之后的链接,并写明期望的结果:

2.3 调试和反馈 🔗

在拿到大模型给的初版代码时,我们需要进行试运行,并将相应的报错(如有)的信息给到大模型,让大模型调整代码。

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) 这样的字符串,将尖括号两边的多余字符串给删掉来解决呢?

于是就有了下面这一次与大模型的对话和尝试:

最终经过尝试,通过下面这一段代码,在导出为 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,持续分享我的心得,感谢您的阅读。