This patch is for a very confusing edge case that came up when I was working on a verbatim link export option for ox-md. I was trying to see what part of org-html-link's code needed to be extracted for `org-md-link'. For a link like:
[[/tmp/tmp-orgtest/emacs][Emacs]] you would expect org-html-link to return: <a href=\"file:///tmp/tmp-orgtest/emacs">Contributing to Org</a> when `org-html-link-use-abs-url' is set to t,`org-html-link-home' is set to a non-nil value and the ':base-directory` key of `org-publish-project-alist' has not been set. Instead `org-html-link' returns: <a href=\"https://www.example.com/file:///en/install-emacs-on-android\">install Emacs</a></p>. As it seems `org-html-link-use-abs-url' and`org-html-link-home' are mostly intended for people who use org-publish, this likely never came up in the wild. But as I would like to reuse these customs for verbatim link export, this needed to be fixed.
From 4cfe916a301342de92846eef118f8f8137b7c89f Mon Sep 17 00:00:00 2001 From: ApollonDeParnasse <[email protected]> Date: Tue, 30 Jun 2026 16:07:54 -0500 Subject: [PATCH] lisp/ox-html.el: Fix org-html-link file path creation bug * lisp/ox-html.el (org-html-link): Use `org-html--create-file-link-path' in order to create the link path of a file link. (org-html--create-file-link-path): Create the path of a file link. * testing/lisp/test-ox-html.el (ox-html/test-org-html-link-use-abs-url): New tests for `org-html-link'. --- lisp/ox-html.el | 36 ++++--- testing/lisp/test-ox-html.el | 190 +++++++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+), 15 deletions(-) diff --git a/lisp/ox-html.el b/lisp/ox-html.el index 74fd198e2..3aff83162 100644 --- a/lisp/ox-html.el +++ b/lisp/ox-html.el @@ -3325,6 +3325,24 @@ images, set it to: info nil 'link) (= link-count 1)))))) +(defun org-html--create-file-link-path (raw-path info) + "Convert RAW-PATH into a HTML file link path. +During publishing, turn absolute file names +belonging to base directory into relative +file names. Otherwise, append `file' protocol +to absolute file name. INFO should be the +export options, as a plist." + (let* ((file-relative-name (org-publish-file-relative-name raw-path info)) + (home (and (plist-get info :html-link-home) + (org-trim (plist-get info :html-link-home))))) + ;; Possibly append `:html-link-home' to relative file + ;; name. + (if (and home + (plist-get info :html-link-use-abs-url) + (not (file-name-absolute-p file-relative-name))) + (file-name-concat (file-name-as-directory home) file-relative-name) + (org-export-file-uri file-relative-name)))) + (defun org-html-link (link desc info) "Transcode a LINK object from Org to HTML. DESC is the description part of the link, or the empty string. @@ -3350,22 +3368,10 @@ INFO is a plist holding contextual information. See (path (cond ((string= "file" type) - ;; During publishing, turn absolute file names belonging - ;; to base directory into relative file names. Otherwise, - ;; append "file" protocol to absolute file name. - (setq raw-path - (org-export-file-uri - (org-publish-file-relative-name raw-path info))) - ;; Possibly append `:html-link-home' to relative file - ;; name. - (let ((home (and (plist-get info :html-link-home) - (org-trim (plist-get info :html-link-home))))) - (when (and home - (plist-get info :html-link-use-abs-url) - (not (file-name-absolute-p raw-path))) - (setq raw-path (concat (file-name-as-directory home) raw-path)))) ;; Maybe turn ".org" into ".html". - (setq raw-path (funcall link-org-files-as-html-maybe raw-path info)) + (setq raw-path (funcall link-org-files-as-html-maybe + (org-html--create-file-link-path raw-path info) + info)) ;; Add search option, if any. A search option can be ;; relative to a custom-id, a headline title, a name or ;; a target. diff --git a/testing/lisp/test-ox-html.el b/testing/lisp/test-ox-html.el index 717838105..1d09b2cd7 100644 --- a/testing/lisp/test-ox-html.el +++ b/testing/lisp/test-ox-html.el @@ -1260,4 +1260,194 @@ entirely." (org-export-string-as "" 'html nil '( :html-doctype "xhtml5" :html-klipsify-src t))))) + + +(ert-deftest ox-html/test-org-html-link-use-abs-url () + "Test `org-html-link-use-abs-url'." + (cl-flet* ((create-test-link-element (test-link-path + &optional test-desc) + (let ((test-desc (if test-desc + (format "[[file:%s][%s]]" test-link-path test-desc) + (format "[[file:%s]]" test-link-path)))) + (org-test-with-temp-text test-desc + (org-element-link-parser))))) + + ;; file is not in publishing directory + (ert-with-temp-directory test-dir + (let* ((test-home "https://www.example.com") + (test-file-name "/en/install-emacs-on-android") + (test-link-element (create-test-link-element + test-file-name + nil)) + (expected-link-path (format "file://%s" test-file-name)) + (expected-link (format "<a href=\"%s\">%s</a>" + expected-link-path + expected-link-path)) + (test-info (list :base-directory test-dir)) + (actual-link (org-html-link + test-link-element + nil + test-info))) + (should (string-equal actual-link expected-link)))) + + (ert-with-temp-directory test-dir + (let* ((test-home "https://www.notabug.com") + (test-file-name "/examples/babel.html") + (test-link-element (create-test-link-element test-file-name)) + (expected-link-path (format "file://%s" test-file-name)) + (expected-link (format "<a href=\"%s\">%s</a>" + expected-link-path + expected-link-path)) + (test-info (list :base-directory test-dir + :test-home test-home)) + (actual-link (org-html-link + test-link-element + nil + test-info))) + (should (string-equal actual-link expected-link)))) + + ;; with `org-html-link-use-abs-url' + (ert-with-temp-directory test-dir + (let* ((test-desc "Install Emacs") + (test-home "https://www.example.com") + (test-file-name "/en/install-emacs-on-android") + (test-link-element (create-test-link-element + test-file-name + test-desc)) + (expected-link (format "<a href=\"file://%s\">%s</a>" + test-file-name + test-desc)) + (test-info (list :base-directory test-dir + :test-home test-home)) + (actual-link (org-html-link + test-link-element + test-desc + test-info))) + (should (string-equal actual-link expected-link)))) + + (ert-with-temp-directory test-dir + (let* ((test-home "https://www.notabug.com") + (test-file-name "/examples/babel.html") + (test-link-element (create-test-link-element test-file-name)) + (expected-link-path (format "file://%s" test-file-name)) + (expected-link (format "<a href=\"%s\">%s</a>" + expected-link-path + expected-link-path)) + (test-info (list :base-directory test-dir + :test-home test-home)) + (actual-link (org-html-link + test-link-element + nil + test-info))) + (should (string-equal actual-link expected-link)))) + + ;; file is in publishing directory + (ert-with-temp-file test-link-path + (let* ((test-desc "Contributing to Org") + (test-home "https://orgmode.org") + (test-dir (file-name-parent-directory test-link-path)) + (test-file-name (file-name-nondirectory test-link-path)) + (test-link-element (create-test-link-element + test-link-path + test-desc)) + (expected-link (format "<a href=\"%s\">%s</a>" + test-file-name + test-desc)) + (test-info (list :base-directory test-dir)) + (actual-link (org-html-link + test-link-element + test-desc + test-info))) + (should (string-equal actual-link expected-link)))) + + (ert-with-temp-file test-link-path + (let* ((test-home "https://mywebsite.com") + (test-dir (file-name-parent-directory test-link-path)) + (test-file-name (file-name-nondirectory test-link-path)) + (test-link-element (create-test-link-element test-link-path)) + (expected-link-path (concat test-home "/" test-file-name)) + (expected-link (format "<a href=\"%s\">%s</a>" + test-file-name + test-file-name)) + (test-info (list :base-directory test-dir)) + (actual-link (org-html-link + test-link-element + nil + test-info))) + (should (string-equal actual-link expected-link)))) + + ;; with `org-html-link-use-abs-url' + (ert-with-temp-file test-link-path + (let* ((test-desc "Contributing to Org") + (test-home "https://orgmode.org") + (test-dir (file-name-parent-directory test-link-path)) + (test-file-name (file-name-nondirectory test-link-path)) + (test-link-element (create-test-link-element + test-link-path + test-desc)) + (expected-link (format "<a href=\"%s/%s\">%s</a>" + test-home + test-file-name + test-desc)) + (test-info (list :base-directory test-dir + :html-link-use-abs-url t + :html-link-home test-home)) + (actual-link (org-html-link + test-link-element + test-desc + test-info))) + (should (string-equal actual-link expected-link)))) + + (ert-with-temp-file test-link-path + (let* ((test-home "https://mywebsite.com") + (test-dir (file-name-parent-directory test-link-path)) + (test-file-name (file-name-nondirectory test-link-path)) + (test-link-element (create-test-link-element test-link-path)) + (expected-link-path (concat test-home "/" test-file-name)) + (expected-link (format "<a href=\"%s\">%s</a>" + expected-link-path + expected-link-path)) + (test-info (list :base-directory test-dir + :html-link-use-abs-url t + :html-link-home test-home)) + (actual-link (org-html-link + test-link-element + nil + test-info))) + (should (string-equal actual-link expected-link)))) + + ;; with `org-html-link-use-abs-url' and no base-directory + (ert-with-temp-file test-link-path + (let* ((test-desc "Contributing to Org") + (test-home "https://orgmode.org") + (test-link-element (create-test-link-element + test-link-path + test-desc)) + (expected-link-path (format "file://%s" test-link-path)) + (expected-link (format "<a href=\"%s\">%s</a>" + expected-link-path + test-desc)) + (test-info (list :html-link-use-abs-url t + :html-link-home test-home)) + (actual-link (org-html-link + test-link-element + test-desc + test-info))) + (should (string-equal actual-link expected-link)))) + + (ert-with-temp-file test-link-path + (let* ((test-home "https://mywebsite.com") + (test-link-element (create-test-link-element test-link-path)) + (expected-link-path (format "file://%s" test-link-path)) + (expected-link (format "<a href=\"%s\">%s</a>" + expected-link-path + expected-link-path)) + (test-info (list :html-link-use-abs-url t + :html-link-home test-home)) + (actual-link (org-html-link + test-link-element + nil + test-info))) + (should (string-equal actual-link expected-link)))))) + ;;; test-ox-html.el ends here -- 2.54.0
