Le sam. 25 juil. 2026 à 10:32, Ihor Radchenko <[email protected]> a écrit :
>
> Earl Chase <[email protected]> writes:
>
> >  etc/ORG-NEWS                 |  21 +++
> >  lisp/ox-html.el              |  55 +++++---
> >  testing/lisp/test-ox-html.el | 241 +++++++++++++++++++++++++++++++++++
>
> I think we should also document the new option in the manual.
>

Done.

> > +(defcustom org-html-base-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'.  If `:base-directory'
> > +is also set, the value of this variable will be ignored."
> > +  :group 'org-export-html
> > +  :type 'string)
>
> Please add :package-version and :safe keywords.
>

Done.

> > +(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* ((html-base-directory (plist-get info :html-base-directory))
> > +         (updated-info (if (and html-base-directory
> > +                                (not (plist-get info
> > +                                                :base-directory)))
> > +                           (plist-put info
> > +                                      :base-directory
> > +                                      html-base-directory)
> > +                         info))
>
> Mutating INFO can cause unintended side effects.
> Rather than trying to change INFO + calling
> org-publish-file-relative-name, we should factor out
> org-publish-file-relative-name into a more global function (in ox.el)
> and use it from ox-html and ox-publish.
> I imagine that function to look like
>
> (org-export-file-relative-name-maybe filename base-directory)
>
> > +         (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.
>

Done.

> *prepend :)
>

Fixed.

> > +    (if (and home
> > +             (plist-get info :html-link-use-abs-url)
> > +             (not (file-name-absolute-p file-relative-name)))
>
> This reads awkward. Maybe we need to name FILE-RELATIVE-NAME variable
> better.
>
> > +(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))))
>
> You can just use org-link-make-string.
>

Done.

> --
> Ihor Radchenko // yantar92,
> Org mode maintainer,
> Learn more about Org mode at <https://orgmode.org/>.
> Support Org development at <https://liberapay.com/org-mode>,
> or support my work at <https://liberapay.com/yantar92>
From 11145ed0466369958d855c47edbe3f9f013f8075 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-base-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-base-directory): New tests for
`org-html-link'.
(ox-html/test-link-home-and-use-abs-url/no-base-directory):
New tests for `org-html-link'.
(ox-html/test-org-html-base-directory/with-base-directory-set):
New tests for `org-html-link'.

* lisp/ox-publish.el (org-publish-file-relative-name-maybe):
Convert filename into a relative filename if it is in a given
base-directory.
(org-publish-file-relative-name): Use
`org-publish-file-relative-name-maybe` to convert filenames
into relative file names.
---
 doc/org-manual.org           |  25 +++-
 etc/ORG-NEWS                 |  21 ++++
 lisp/ox-html.el              |  52 +++++---
 lisp/ox-publish.el           |  18 ++-
 testing/lisp/test-ox-html.el | 236 +++++++++++++++++++++++++++++++++++
 5 files changed, 329 insertions(+), 23 deletions(-)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index fa0c71c30..aa5b99169 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -13826,8 +13826,29 @@ location, then the converted links should work without any further
 manual intervention.  However, to disable this automatic path
 translation, set ~org-html-link-org-files-as-html~ to ~nil~.  When
 disabled, the HTML export backend substitutes the ID-based links in
-the HTML output.  For more about linking files when publishing to
-a directory, see [[*Publishing links]].
+the HTML output.
+
+#+vindex: org-html-base-directory
+~org-html-base-directory~ can be used to set the directory of
+the project that you intend to export. When used in conjunction
+with ~org-html-link-home~ and ~org-html-link-use-abs-url~, file
+links to files in ~org-html-base-directory~ will have the value
+of ~org-html-link-home~ prepended to them.  For example, when
+~org-html-base-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
+
+When ~:base-directory~ and ~org-html-base-directory~ are both set,
+~:base-directory~ takes precedence, i.e., the value of
+~org-html-base-directory~ will be ignored.
+
+~org-html-base-directory~ is the equivalent of the ~:base-directory~
+key of ~org-publish-project-alist~. For more about linking files when
+publishing to a directory, see [[*Publishing links]].
 
 Org files can also have special directives to the HTML export
 backend.  For example, by using =#+ATTR_HTML= lines to specify new
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 2e9e8e35b..93dd1ac47 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -260,6 +260,27 @@ 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 ~org-html-base-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 ~org-html-base-directory~
+will have the value of ~org-html-link-home~ prepended to them.  For example,
+when ~org-html-base-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
+
+When ~:base-directory~ and ~org-html-base-directory~ are both set,
+~:base-directory~ takes precedence, i.e., the value of
+~org-html-base-directory~ will be ignored.
+
 ** 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..4041b0d9f 100644
--- a/lisp/ox-html.el
+++ b/lisp/ox-html.el
@@ -44,6 +44,7 @@
 ;;; Function Declarations
 
 (declare-function htmlize-region "ext:htmlize" (beg end))
+(declare-function org-publish-file-relative-name-maybe "ox-publish" (filename base-directory))
 
 (defvar htmlize-css-name-prefix)
 (defvar htmlize-output-type)
@@ -122,6 +123,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-base-directory "HTML_BASE_DIRECTORY" nil org-html-base-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 +1503,16 @@ ignored."
   :group 'org-export-html
   :type 'string)
 
+(defcustom org-html-base-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'.  If `:base-directory'
+is also set, the value of this variable will be ignored."
+  :group 'org-export-html
+  :package-version '(Org . "10.0")
+  :safe #'stringp
+  :type 'string)
+
 ;;;; Template :: Scripts
 
 (defcustom org-html-head-include-scripts nil
@@ -3325,6 +3337,28 @@ 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,
+prepend `file' protocol to absolute file name.  INFO
+should be the export options, as a plist."
+  (let* ((html-base-directory (or (plist-get info :base-directory)
+                                  (plist-get info :html-base-directory)))
+         (maybe-file-relative-name (org-publish-file-relative-name-maybe
+                              raw-path html-base-directory))
+         (home (and (plist-get info :html-link-home)
+                    (org-trim (plist-get info :html-link-home)))))
+    ;; Possibly prepend `:html-link-home' to relative file
+    ;; name.
+    (if (and home
+             (plist-get info :html-link-use-abs-url)
+             (not (file-name-absolute-p maybe-file-relative-name)))
+        (file-name-concat
+         (file-name-as-directory home)
+         maybe-file-relative-name)
+      (org-export-file-uri maybe-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 +3384,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/lisp/ox-publish.el b/lisp/ox-publish.el
index d51d68f31..0b15acef9 100644
--- a/lisp/ox-publish.el
+++ b/lisp/ox-publish.el
@@ -1201,17 +1201,23 @@ references with `org-export-get-reference'."
 	 (org-publish-cache-set-file-property filename :crossrefs crossrefs)
 	 (org-export-format-reference new)))))))
 
+(defun org-publish-file-relative-name-maybe (filename base-directory)
+  "Maybe convert FILENAME to a relative filename.
+FILENAME will only be converted to a relative filename
+if it is in BASE-DIRECTORY.  Otherwise, FILENAME will simply
+be returned."
+  (if (and base-directory
+       (file-name-absolute-p filename)
+	   (file-in-directory-p filename base-directory))
+	(file-relative-name filename base-directory)
+      filename))
+
 (defun org-publish-file-relative-name (filename info)
   "Convert FILENAME to be relative to current project's base directory.
 INFO is the plist containing the current export state.  The
 function does not change relative file names."
   (let ((base (plist-get info :base-directory)))
-    (if (and base
-	     (file-name-absolute-p filename)
-	     (file-in-directory-p filename base))
-	(file-relative-name filename base)
-      filename)))
-
+    (org-publish-file-relative-name-maybe filename base)))
 
 
 ;;; Caching functions
diff --git a/testing/lisp/test-ox-html.el b/testing/lisp/test-ox-html.el
index 717838105..85d038cad 100644
--- a/testing/lisp/test-ox-html.el
+++ b/testing/lisp/test-ox-html.el
@@ -1260,4 +1260,240 @@ 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 (org-link-make-string test-link-path test-desc)))
+    (org-test-with-temp-text test-desc
+      (org-element-link-parser))))
+
+(ert-deftest ox-html/test-org-html-base-directory ()
+  "Test `org-html-base-directory'."
+  ;; file is not in html-base-directory
+  (org-test-with-temp-text-in-file ""
+    (let* ((test-desc "Install Emacs")
+           (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
+                               test-desc))
+           (expected-link-path (format "file://%s" test-file-name))
+           (expected-link (format "<a href=\"%s\">%s</a>"
+                                  expected-link-path
+                                  test-desc))
+           (test-info (list :html-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))))
+
+  (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-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))))
+
+  ;; file is in html-base-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-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))))
+
+  (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-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)))))
+
+(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)))))
+
+(ert-deftest ox-html/test-org-html-base-directory/with-base-directory-set ()
+  "Assert `:base-directory' has precendence over `org-html-base-directory'."
+  ;; file is in `:base-directory'
+  ;; and is not in html-base-directory
+  (org-test-with-temp-text-in-file ""
+    (let* ((test-desc "Contributing to Org")
+           (test-home "https://orgmode.org";)
+           (test-base-directory (file-name-parent-directory
+                                 buffer-file-name))
+           (test-html-base-directory "/my/project/directory")
+           (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 :base-directory test-base-directory
+                            :html-base-directory test-html-base-directory
+                            :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-base-directory (file-name-parent-directory
+                                 buffer-file-name))
+           (test-html-base-directory "~/mywebsite")
+           (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 :base-directory test-base-directory
+                            :html-base-directory test-html-base-directory
+                            :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 not in `:base-directory'
+  ;; and is in html-base-directory
+  (org-test-with-temp-text-in-file ""
+    (let* ((test-desc "Contributing to Org")
+           (test-home "https://orgmode.org";)
+           (test-base-directory "/org/base")
+           (test-html-base-directory (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 (format "file://%s" buffer-file-name))
+           (expected-link (format "<a href=\"%s\">%s</a>"
+                                  expected-link-path
+                                  test-desc))
+           (test-info (list :base-directory test-base-directory
+                            :html-base-directory test-html-base-directory
+                            :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-base-directory "~/website/publish-dir")
+           (test-html-base-directory (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 (format "file://%s" buffer-file-name))
+           (expected-link (format "<a href=\"%s\">%s</a>"
+                                  expected-link-path
+                                  expected-link-path))
+           (test-info (list :base-directory test-base-directory
+                            :html-base-directory test-html-base-directory
+                            :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