Max Nikulin <[email protected]> writes:

> On 07/10/2025 05:05, Steven Allen wrote:
>> I've attached two patches to implement this change. The first adds a
>> convenience org-export-link-with-backend function and implements the
>> change for man, docview, and doi (where the behavior change is fairly
>> minimal). The second extends this change to cover info links as well.
>
> I like the idea to delegate export to a backend-specific function.
>
> Please, be skeptical in respect to the following comments. I may be wrong.
>
> Shouldn't org-manual.org "man:" link example for custom link types be
> updated as well?

Done.

>> +++ b/etc/ORG-NEWS
>
>> +*** =man=, =docview=, and =doi= links are exported as HTTP links by default
>
>> +=man:foo=, etc.) with all other (non-hard-coded) backends.  Now, such
>> +links are always transformed into =http= links before exporting,
>> +regardless of the backend.  Importantly, this means that =docview= and
>
> I do not think that doi links should be converted to http ones in the
> case of the ascii backend. For man links without description in plain
> text the convention is e.g. man(1), but it is more subtle since section
> sometimes is not known. From my point of view, http URL may make text
> harder to read.

I generally agree but here I'm just preserving existing behavior.

>> +++ b/lisp/ol-doi.el
>
>> +(require 'ox)
>
> I would consider (declare-function ...) to avoid eager loading of ox.

Good point, but I've also added an autoload cookie (I could,
alternatively, move the require statement into the defun).

>> +++ b/lisp/ox.el
>
>> +   (car (org-element-parse-secondary-string
>> +         (org-link-make-string link desc) '(link)))
>
> I am surprised that format-and-parse approach is necessary. Generally, I
> expect direct constructing of an element.

It was just the most easiest way to implement this with the fewest edge
cases to worry about. E.g., this way will correctly handle coderef,
custom-id, etc.

But I'm happy to change it to use org-element-create instead.

> I am afraid of obscure errors due to mistakes in custom user code
> causing infinite loop. On the other hand some code to detect recursive
> call with earlier seen type may have some performance impact.

Because we're formatting then parsing? Or because
org-export-link-with-backend recursively calls back into the export
backend?

IMO, the former isn't really a concern (parsing/exporting are two
different paths). The latter is a concern but I don't think it's worth
it to try to handle that case specifically (we'll error out either way).

>
>> +++ b/lisp/ol-info.el
>
>> +    (if (eq backend 'texinfo)
>
> `org-export-derived-backend-p' should be better.

Fixed.

>From d1b8a8b865f9061a3b20d5016417385c14b30b56 Mon Sep 17 00:00:00 2001
From: Steven Allen <[email protected]>
Date: Mon, 6 Oct 2025 12:04:54 -0700
Subject: [PATCH 1/2] org-link: Delegate to org-export backend where possible

Instead of trying to support each org-export backend in each org-link
:export function, delegate to the org-export backends where possible.

* lisp/ox.el (org-export-link-with-backend): Add a new function to
export a link with a specific org-export backend.
* lisp/ol-docview.el (org-docview-export):
* lisp/ol-doi.el (org-dlink-doi-export):
* lisp/ol-man.el (org-man-export): Use this function to delegate link
formatting to the appropriate backend where possible.
* etc/ORG-NEWS: Document the new function.
---
 doc/org-manual.org | 11 +++--------
 etc/ORG-NEWS       | 33 +++++++++++++++++++++++++++++++++
 lisp/ol-docview.el | 17 +++++++----------
 lisp/ol-doi.el     | 18 +++---------------
 lisp/ol-man.el     | 20 ++++++++------------
 lisp/ox.el         | 18 ++++++++++++++++++
 6 files changed, 72 insertions(+), 45 deletions(-)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index 8c6962d4b..21a778e0b 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -21967,16 +21967,11 @@ ** Adding Hyperlink Types
       (match-string 1 (buffer-name))
     (error "Cannot create link to this man page")))
 
-(defun org-man-export (link description format _)
+(defun org-man-export (link description backend info)
   "Export a man page link from Org files."
   (let ((path (format "http://man.he.net/?topic=%s&section=all"; link))
-        (desc (or description link)))
-    (pcase format
-      (`html (format "<a target=\"_blank\" href=\"%s\">%s</a>" path desc))
-      (`latex (format "\\href{%s}{%s}" path desc))
-      (`texinfo (format "@uref{%s,%s}" path desc))
-      (`ascii (format "%s (%s)" desc path))
-      (t path))))
+	(desc (or description link)))
+    (org-export-link-with-backend backend path desc info)))
 
 (provide ol-man)
 ;;; ol-man.el ends here
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 87fbf8f06..b469cba84 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -527,6 +527,28 @@ accommodate even a single character of the headline, after accounting for spaces
 and the surrounding parentheses, it will omit the headline entirely and just
 show as much of the clock as fits under the limit.
 
+*** New function ~org-export-link-with-backend~ to export a link with a given backend
+
+The new ~org-export-link-with-backend~ function can be used to export
+a link with by delegating to an org-export backend.  This is primarily
+useful when implementing a custom org-link exporter (see the
+documentation for ~:export~ on ~org-link-parameters~) that (a) wants
+to delegate to the current org-export backend but (b) needs to
+transform the link first.
+
+For example, ~org-man-export~ transforms =man:foo= links into
+=http://= links then delegates current to the org-export backend's
+handling of =http://= links:
+
+#+begin_src elisp
+(defun org-man-export (link description backend info)
+  (let ((path (format "http://man.he.net/?topic=%s&section=all"; link))
+	(desc (or description link)))
+    (org-export-link-with-backend backend path desc info)))
+
+(org-link-set-parameters "man" :export #'org-man-export ...)
+#+end_src
+
 ** Removed or renamed functions and variables
 
 *** ~org-cycle-display-inline-images~ is renamed to ~org-cycle-display-link-previews~
@@ -739,6 +761,17 @@ known to contain headlines with IDs.  You can use new option
 ~org-id-completion-targets~ to change where the candidates are
 searched.
 
+*** =man=, =docview=, and =doi= links are exported as HTTP links by default
+
+Previously, =man=, =docview=, and =doi= links were exported as =http=
+links when exporting with specific hard-coded backends (different ones
+depending on the link type) but were otherwise exported literally (as
+=man:foo=, etc.) with all other (non-hard-coded) backends.  Now, such
+links are always transformed into =http= links before exporting,
+regardless of the backend.  Importantly, this means that =docview= and
+=doi= links are converted to =http= links before being exported to
+markdown.
+
 * Version 9.7
 
 ** Important announcements and breaking changes
diff --git a/lisp/ol-docview.el b/lisp/ol-docview.el
index be09328df..3a36c249a 100644
--- a/lisp/ol-docview.el
+++ b/lisp/ol-docview.el
@@ -51,24 +51,21 @@
 (declare-function doc-view-goto-page "doc-view" (page))
 (declare-function image-mode-window-get "image-mode" (prop &optional winprops))
 (declare-function org-open-file "org" (path &optional in-emacs line search))
+(declare-function org-export-link-with-backend "org-export"
+                  (backend link &optional desc info))
 
 (org-link-set-parameters "docview"
 			 :follow #'org-docview-open
 			 :export #'org-docview-export
 			 :store #'org-docview-store-link)
 
-(defun org-docview-export (link description backend _info)
-  "Export a docview LINK with DESCRIPTION for BACKEND."
+(defun org-docview-export (link desc backend info)
+  "Export a docview LINK with DESC for BACKEND.
+INFO is a plist containing the export parameters."
   (let ((path (if (string-match "\\(.+\\)::.+" link) (match-string 1 link)
-		link))
-        (desc (or description link)))
+		link)))
     (when (stringp path)
-      (setq path (expand-file-name path))
-      (cond
-       ((eq backend 'html) (format "<a href=\"%s\">%s</a>" path desc))
-       ((eq backend 'latex) (format "\\href{%s}{%s}" path desc))
-       ((eq backend 'ascii) (format "[%s] (<%s>)" desc path))
-       (t path)))))
+      (org-export-link-with-backend backend (expand-file-name path) desc info))))
 
 (defun org-docview-open (link _)
   "Open docview: LINK."
diff --git a/lisp/ol-doi.el b/lisp/ol-doi.el
index fba1c6351..dce27e2af 100644
--- a/lisp/ol-doi.el
+++ b/lisp/ol-doi.el
@@ -44,27 +44,15 @@ defun org-link-doi-open
 ARG is passed to `browse-url'."
   (browse-url (url-encode-url (concat org-link-doi-server-url path)) arg))
 
+(declare-function org-export-link-with-backend "ox"
+                  (backend link &optional desc info))
 (defun org-link-doi-export (path desc backend info)
   "Export a \"doi\" type link.
 PATH is the DOI name.  DESC is the description of the link, or
 nil.  BACKEND is a symbol representing the backend used for
 export.  INFO is a plist containing the export parameters."
   (let ((uri (concat org-link-doi-server-url path)))
-    (pcase backend
-      (`html
-       (format "<a href=\"%s\">%s</a>" uri (or desc uri)))
-      (`latex
-       (if desc (format "\\href{%s}{%s}" uri desc)
-	 (format "\\url{%s}" uri)))
-      (`ascii
-       (if (not desc) (format "<%s>" uri)
-         (concat (format "[%s]" desc)
-		 (and (not (plist-get info :ascii-links-to-notes))
-		      (format " (<%s>)" uri)))))
-      (`texinfo
-       (if (not desc) (format "@uref{%s}" uri)
-         (format "@uref{%s, %s}" uri desc)))
-      (_ uri))))
+    (org-export-link-with-backend backend uri desc info)))
 
 (org-link-set-parameters "doi"
                          :follow #'org-link-doi-open
diff --git a/lisp/ol-man.el b/lisp/ol-man.el
index 2511d8166..d266f06af 100644
--- a/lisp/ol-man.el
+++ b/lisp/ol-man.el
@@ -100,18 +100,14 @@ defun org-man-get-page-name
       (match-string 1 (buffer-name))
     (error "Cannot create link to this man page")))
 
-(defun org-man-export (link description backend)
-  "Export a man page LINK with DESCRIPTION.
-BACKEND is the current export backend."
-  (let ((path (format "http://man.he.net/?topic=%s&section=all"; link))
-	(desc (or description link)))
-    (cond
-     ((eq backend 'html) (format "<a target=\"_blank\" href=\"%s\">%s</a>" path desc))
-     ((eq backend 'latex) (format "\\href{%s}{%s}" path desc))
-     ((eq backend 'texinfo) (format "@uref{%s,%s}" path desc))
-     ((eq backend 'ascii) (format "[%s] (<%s>)" desc path))
-     ((eq backend 'md) (format "[%s](%s)" desc path))
-     (t path))))
+(declare-function org-export-link-with-backend "org-export"
+                  (backend link &optional desc info))
+(defun org-man-export (link desc backend &optional info)
+  "Export a man page LINK with DESC.
+BACKEND is the current export backend.  INFO is a plist containing the
+export parameters."
+  (let ((path (format "http://man.he.net/?topic=%s&section=all"; link)))
+    (org-export-link-with-backend backend path desc info)))
 
 (defvar Man-completion-cache) ; Defined in `man'.
 (defun org-man-complete (&optional _arg)
diff --git a/lisp/ox.el b/lisp/ox.el
index 76af098d4..2b1f07a83 100644
--- a/lisp/ox.el
+++ b/lisp/ox.el
@@ -4334,6 +4334,10 @@ defun org-export-get-date
 ;; `org-export-file-uri' expands a filename as stored in :path value
 ;;  of a "file" link into a file URI.
 ;;
+;; `org-export-link-with-backend' exports a link with the given
+;; backend, useful when a link needs to be transformed before it can
+;; be exported.
+;;
 ;; Broken links raise a `org-link-broken' error, which is caught by
 ;; `org-export-data' for further processing, depending on
 ;; `org-export-with-broken-links' value.
@@ -4734,6 +4738,20 @@ defun org-export-link-localise
                  (org-element-property :raw-link link))))))
   link)
 
+;;;###autoload
+(defun org-export-link-with-backend (backend link &optional desc info)
+  "Export the given LINK with the specified BACKEND.
+BACKEND is an export backend, as returned by, e.g.,
+`org-export-create-backend', or a symbol referring to a registered
+backend.  LINK is the link target and DESC is an optional link
+description.  INFO, when non-nil, is the communication channel used
+for export, as a plist."
+  (org-export-with-backend
+   backend
+   (car (org-element-parse-secondary-string
+         (org-link-make-string link desc) '(link)))
+   desc info))
+
 ;;;; For References
 ;;
 ;; `org-export-get-reference' associate a unique reference for any
-- 
2.51.0

>From 498afd6bfa2dddc4b47ec62548d5af7f9d4878e3 Mon Sep 17 00:00:00 2001
From: Steven Allen <[email protected]>
Date: Mon, 6 Oct 2025 13:01:46 -0700
Subject: [PATCH 2/2] ol-info: Export links as HTTP on all backends except
 texinfo

Previously, when exporting links, ol-info would:

1. Export links as http(s) when the backend was html.
2. Export links as texinfo refs when the backend was texinfo.
3. Delegate to the backend (exporting as raw info: links).

Now, ol-info always exports links as http(s) unless the backend is
texinfo. This makes info links work when, e.g., exporting to PDFs,
markdown, etc. (same as man links).

* lisp/ol-info.el (org-info-export): Always export links as http(s)
links, except when the org-export backend is texinfo.
---
 etc/ORG-NEWS    |  5 ++++-
 lisp/ol-info.el | 28 ++++++++++++++++------------
 2 files changed, 20 insertions(+), 13 deletions(-)

diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index b469cba84..544f4b183 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -761,7 +761,7 @@ known to contain headlines with IDs.  You can use new option
 ~org-id-completion-targets~ to change where the candidates are
 searched.
 
-*** =man=, =docview=, and =doi= links are exported as HTTP links by default
+*** =man=, =docview=, =doi=, and =info= links are exported as HTTP links by default
 
 Previously, =man=, =docview=, and =doi= links were exported as =http=
 links when exporting with specific hard-coded backends (different ones
@@ -772,6 +772,9 @@ regardless of the backend.  Importantly, this means that =docview= and
 =doi= links are converted to =http= links before being exported to
 markdown.
 
+Additionally, =info= links will now also be exported as =http= links
+/except/ when the export backend is texinfo.
+
 * Version 9.7
 
 ** Important announcements and breaking changes
diff --git a/lisp/ol-info.el b/lisp/ol-info.el
index a835bd403..cbda38458 100644
--- a/lisp/ol-info.el
+++ b/lisp/ol-info.el
@@ -39,6 +39,10 @@
 
 (declare-function Info-find-node "info"
                   (filename nodename &optional no-going-back strict-case))
+(declare-function org-export-derived-backend-p "org-export"
+                  (backend &rest backends))
+(declare-function org-export-link-with-backend "org-export"
+                  (backend link &optional desc info))
 (defvar Info-current-file)
 (defvar Info-current-node)
 
@@ -178,20 +182,20 @@ defun org-info--expand-node-name
 	  ((string-match-p "\\`[0-9]" node) (concat "g_t" node))
 	  (t node))))
 
-(defun org-info-export (path desc format)
+(defun org-info-export (path desc backend &optional info)
   "Export an Info link.
-See `org-link-parameters' for details about PATH, DESC and FORMAT."
+See `org-link-parameters' for details about PATH, DESC, BACKEND, and
+INFO."
   (pcase-let ((`(,manual . ,node) (org-info--link-file-node path)))
-    (pcase format
-      (`html
-       (format "<a href=\"%s#%s\">%s</a>"
-	       (org-info-map-html-url manual)
-	       (org-info--expand-node-name node)
-	       (or desc path)))
-      (`texinfo
-       (let ((title (or desc "")))
-	 (format "@ref{%s,%s,,%s,}" node title manual)))
-      (_ nil))))
+    (if (org-export-derived-backend-p backend 'texinfo)
+        (format "@ref{%s,%s,,%s,}" node (or desc "") manual)
+      (org-export-link-with-backend
+       backend
+       (format "%s#%s"
+               (org-info-map-html-url manual)
+               (org-info--expand-node-name node))
+       (or desc path)
+       info))))
 
 (provide 'ol-info)
 
-- 
2.51.0

Reply via email to