> >> Also, update the commit message. > > > > I updated the commit message. Are manual updates supposed to go into > > the commit message? > > Yes. Manual is just anther source file :) >
Done. > > Subject: [PATCH] lisp/ox-html: New export option for file links > > ... > > * lisp/ox-publish (org-publish-file-relative-name): Use > > lisp/ox-publish.el > Fixed. > > +#+begin_example > > +"<a href=\"https://orgmode.org/contribute\">Contributing to Org</a>" > > +#+end_example > > Maybe just > <a href=....> without all those Elisp escapes. > Done. > > --- 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-export-file-relative-name-maybe "ox-publish" > > (filename base-directory)) > > Declare is no longer needed here. > Done. > > diff --git a/lisp/ox-publish.el b/lisp/ox-publish.el > > index d51d68f31..27c1a3086 100644 > > --- a/lisp/ox-publish.el > > +++ b/lisp/ox-publish.el > > @@ -45,6 +45,7 @@ > > (require 'ox) > > > > (declare-function org-at-heading-p "org" (&optional _)) > > +(declare-function org-export-file-relative-name-maybe "ox-publish" > > (filename base-directory)) > > Declare is no longer needed here. > Done. > -- > Ihor Radchenko // yantar92, > Org mode maintainer, > Learn more about Org mode at <https://orgmode.org/>. > Support Org development at o<https://liberapay.com/org-mode>, > or support my work at <https://liberapay.com/yantar92>
From 3fd8c5460c7ea662925bdadb57aa251df54ec072 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 export option for file links * lisp/ox-html.el (org-html-base-directory): New export option for file links. (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.el (org-export-file-relative-name-maybe): Convert filename into a relative filename if it is in a given base-directory. * lisp/ox-publish.el (org-publish-file-relative-name): Use `org-export-file-relative-name-maybe` to convert filenames into relative file names. * doc/org-manual.org (HTML): Document the new export option. --- doc/org-manual.org | 32 ++++- etc/ORG-NEWS | 27 ++++ lisp/ox-html.el | 52 +++++--- lisp/ox-publish.el | 7 +- lisp/ox.el | 11 ++ testing/lisp/test-ox-html.el | 236 +++++++++++++++++++++++++++++++++++ 6 files changed, 342 insertions(+), 23 deletions(-) diff --git a/doc/org-manual.org b/doc/org-manual.org index fa0c71c30..bd05fe419 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -13570,6 +13570,13 @@ settings described in [[*Export Settings]]. #+vindex: org-html-link-up The URL for the up link of exported HTML pages (~org-html-link-up~). +- =HTML_BASE_DIRECTORY= :: + + #+cindex: @samp{HTML_BASE_DIRECTORY}, keyword + #+vindex: org-html-base-directory + The directory of the project that you intend to + export (~org-html-base-directory~). + - =HTML_MATHJAX= :: #+cindex: @samp{HTML_MATHJAX}, keyword @@ -13826,8 +13833,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..f5fe7e2e9 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -260,6 +260,33 @@ 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 export option ~org-html-base-directory~ + +This export 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. + +The option can be customized either by + +1. setting the global variable ~org-html-base-directory~ +2. setting the file local keyword =HTML_BASE_DIRECTORY= + + ** 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..e9ac48d20 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-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 +1502,17 @@ 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") + :type '(choice (const :tag "No base directory" nil) + (string :tag "Base directory of the project")) + :safe #'string-or-null-p) + ;;;; 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-export-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..5fe736b09 100644 --- a/lisp/ox-publish.el +++ b/lisp/ox-publish.el @@ -1206,12 +1206,7 @@ references with `org-export-get-reference'." 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-export-file-relative-name-maybe filename base))) ;;; Caching functions diff --git a/lisp/ox.el b/lisp/ox.el index 90c853d76..bd63423bc 100644 --- a/lisp/ox.el +++ b/lisp/ox.el @@ -4742,6 +4742,17 @@ downloaded copy. Otherwise, return unchanged LINK." (org-element-property :raw-link link)))))) link) +(defun org-export-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)) + ;;;; For References ;; ;; `org-export-get-reference' associate a unique reference for any 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
