Related-to: 
https://list.orgmode.org/ca+hmbpnfxe5cick5cw19_enfj2_gp84xxv15coxqlkcn-8n...@mail.gmail.com/

I canceled the above patch because I realized that it would be
difficult to explain what I was trying to fix without implementing the
whole idea. When I looked into `org-html-link-home' and
`org-html-link-use-abs-url' as options for verbatim link export for
ox-html and ox-md, I saw that verbatim link export had already been
implemented for ox-publish. For ox-publish, if `org-html-link-home'
and `org-html-link-use-abs-url' are set then any files URIs that
resolve to your project's base-directory will be converted to relative
URIs and then the value of `org-html-link-home' will be appended to
these URIs. So, I thought that the easiest way to implement verbatim
link export. But there was one issue.

Before this patch, if you set `org-html-link-home' and
`org-html-link-use-abs-url' without setting a base-directory, the
value of `org-html-link-home' would still be appended to the file uri.
For example, when `org-html-link-use-abs-url' is set to
t,`org-html-link-home' is set to "https://www.example.com"; value and
the ':base-directory` key of `org-publish-project-alist' has not been
set, you would expect `org-html-link' to return:

<a href=\"file:///my/project/en/install-emacs-on-android">Install Emacs</a>

when given:

[[/my/project/en/install-emacs-on-android][Install Emacs]]


Instead `org-html-link' returns:

<a 
href=\"https://www.example.com/file:///my/project/install-emacs-on-android\";>install
Emacs</a></p>.

My patch introduces a new custom variable named
`org-html-export-directory'. With `org-html-export-directory' set to
"/my/project", `org-html-link-use-abs-url' set to t and
`org-html-link-home' set to "https://www.example.com";, `org-html-link'
will return:

<a href=\"https://www.example.com/en/install-emacs-on-android\";>Install
Emacs</a></p>

when given:

[[/my/project/en/install-emacs-on-android][Install Emacs]]

Additionally, if you set `org-html-link-use-abs-url' to t,
`org-html-link-home' to "https://www.example.com"; and you forgot or
choose to not set `org-html-export-directory', `org-html-link' will
return:

<a href=\"file:///my/project/en/install-emacs-on-android">Install Emacs</a>

when given:

[[/my/project/en/install-emacs-on-android][Install Emacs]]

In summary, `org-html-export-directory' allows you to set the
base-directory key of `org-publish-project-alist' even when you are
not publishing. But if you forgot or choose to not set that variable,
a valid link will still be exported even when you have set
`org-html-link-use-abs-url' and `org-html-link-home' to non-nil
values.
From ef88445054ae03540e12ff258d4c152dfbe707df 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: New custom variable for the file link export
 process

* lisp/ox-html.el (org-html-export-directory): New custom variable
for the file link export process.
(org-html--create-file-link-path): Create file link paths for
`org-html-link'.
(org-html-link): Have `org-html--create-file-link-path' create
file link paths.

* testing/lisp/test-ox-html.el (test-ox-html-create-test-link-element):
Helper function for `org-html-link' tests.
(ox-html/test-org-html-link-use-abs-url): New tests for
`org-html-link'.
(ox-html/test-link-home-and-use-abs-url/no-base-directory):
New tests for `org-html-link'.
---
 etc/ORG-NEWS                 |  18 +++++
 lisp/ox-html.el              |  51 +++++++++----
 testing/lisp/test-ox-html.el | 135 +++++++++++++++++++++++++++++++++++
 3 files changed, 189 insertions(+), 15 deletions(-)

diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index bb965c02a..36f65bc57 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -251,6 +251,24 @@ non-org-mode files, or any other place where org can not use the
 link context to create a description.  See its docstring for more
 information.
 
+*** New custom variable ~ox-html-export-directory~
+
+This option, nil by default, allows you to set the directory
+of the project that you intend to export.  This variable is the
+equivalent of the ~:base-directory~ key of ~org-publish-project-alist~.
+It is intended for people who use ox-html simply for exporting as
+opposed to publishing.  When used in conjunction with ~org-html-link-home~
+and ~org-html-link-use-abs-url~, file links to files in your export directory
+will have the value of ~org-html-link-home~ appended to them.  For example,
+when ~org-html-export-directory~ is set to =~/my/project=, ~org-html-link-home~
+is set to =https://orgmode.org= and ~org-html-link-use-abs-url~ is set to ~t~,
+~[[~/my/project/contribute][Contributing to Org]]~ will be exported as
+
+#+begin_example
+"<a href=\"https://orgmode.org/contribute\";>Contributing to Org</a>"
+#+end_example
+
+
 ** New functions and changes in function arguments
 
 # This also includes changes in function behavior from Elisp perspective.
diff --git a/lisp/ox-html.el b/lisp/ox-html.el
index 74fd198e2..601a2d059 100644
--- a/lisp/ox-html.el
+++ b/lisp/ox-html.el
@@ -122,6 +122,7 @@
     (:html-link-use-abs-url nil "html-link-use-abs-url" org-html-link-use-abs-url)
     (:html-link-home "HTML_LINK_HOME" nil org-html-link-home)
     (:html-link-up "HTML_LINK_UP" nil org-html-link-up)
+    (:html-export-directory "HTML_EXPORT_DIRECTORY" nil org-html-export-directory)
     (:html-mathjax "HTML_MATHJAX" nil "" space)
     (:html-equation-reference-format "HTML_EQUATION_REFERENCE_FORMAT" nil org-html-equation-reference-format t)
     (:html-postamble nil "html-postamble" org-html-postamble)
@@ -1501,6 +1502,13 @@ ignored."
   :group 'org-export-html
   :type 'string)
 
+(defcustom org-html-export-directory nil
+  "Directory of the project that you intend to export.
+This variable is the equivalent of the `:base-directory'
+key of `org-publish-project-alist'."
+  :group 'org-export-html
+  :type 'string)
+
 ;;;; Template :: Scripts
 
 (defcustom org-html-head-include-scripts nil
@@ -3325,6 +3333,31 @@ 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* ((export-directory (plist-get info :html-export-directory))
+         (updated-info (if export-directory
+                           (plist-put info
+                                      :base-directory export-directory)
+                         info))
+         (file-relative-name (org-publish-file-relative-name
+                              raw-path updated-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 +3383,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..6a28525c0 100644
--- a/testing/lisp/test-ox-html.el
+++ b/testing/lisp/test-ox-html.el
@@ -1260,4 +1260,139 @@ entirely."
     (org-export-string-as "" 'html nil
                           '( :html-doctype "xhtml5"
                              :html-klipsify-src t)))))
+
+;;; Rendering Links
+
+(defun test-ox-html-create-test-link-element (test-link-path
+                                              &optional test-desc)
+  "Helper function for `ox-html-link' tests.
+Uses TEST-LINK-PATH and TEST-DESC to create a file link.
+That file link will then be converted into an org-element."
+  (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))))
+
+(ert-deftest ox-html/test-ox-html-export-directory ()
+  "Test `org-html-export-directory'."
+  ;; file is not in export directory
+  (org-test-with-temp-text-in-file ""
+    (let* ((test-dir (file-name-parent-directory buffer-file-name))
+           (test-home "https://www.example.com";)
+           (test-file-name "/en/install-emacs-on-android")
+           (test-link-element (test-ox-html-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 :html-export-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))))
+
+  (org-test-with-temp-text-in-file ""
+    (let* ((test-dir (file-name-parent-directory buffer-file-name))
+           (test-home "https://www.notabug.com";)
+           (test-file-name "/examples/babel.html")
+           (test-link-element (test-ox-html-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 :html-export-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))))
+
+  ;; file is in export directory
+  (org-test-with-temp-text-in-file ""
+    (let* ((test-desc "Contributing to Org")
+           (test-home "https://orgmode.org";)
+           (test-dir (file-name-parent-directory buffer-file-name))
+           (test-file-name (file-name-nondirectory buffer-file-name))
+           (test-link-element (test-ox-html-create-test-link-element
+                               buffer-file-name
+                               test-desc))
+           (expected-link-path
+            (file-name-concat test-home test-file-name))
+           (expected-link (format "<a href=\"%s\">%s</a>"
+                                  expected-link-path
+                                  test-desc))
+           (test-info (list :html-export-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))))
+
+  (org-test-with-temp-text-in-file ""
+    (let* ((test-home "https://mywebsite.com";)
+           (test-dir (file-name-parent-directory buffer-file-name))
+           (test-file-name (file-name-nondirectory buffer-file-name))
+           (test-link-element (test-ox-html-create-test-link-element buffer-file-name))
+           (expected-link-path
+            (file-name-concat test-home test-file-name))
+           (expected-link (format "<a href=\"%s\">%s</a>"
+                                  expected-link-path
+                                  expected-link-path))
+           (test-info (list :html-export-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)))))
+
+(ert-deftest ox-html/test-link-home-and-use-abs-url/no-base-directory ()
+  "Test `org-html-link-use-abs-url' with `org-html-link-home'."
+  (ert-with-temp-file test-link-path
+    (let* ((test-desc "Contributing to Org")
+           (test-home "https://orgmode.org";)
+           (test-link-element (test-ox-html-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 (test-ox-html-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

Reply via email to