diff --git a/lisp/ox-html.el b/lisp/ox-html.el
index 8ce4fb6ad..fe8209997 100644
--- a/lisp/ox-html.el
+++ b/lisp/ox-html.el
@@ -2328,15 +2328,7 @@ INFO is a plist used as a communication channel."
(org-element-property :priority headline)))
(text (org-export-data-with-backend
(org-export-get-alt-title headline info)
- ;; Create an anonymous back-end that will ignore any
- ;; footnote-reference, link, radio-target and target
- ;; in table of contents.
- (org-export-create-backend
- :parent 'html
- :transcoders '((footnote-reference . ignore)
- (link . (lambda (object c i) c))
- (radio-target . (lambda (object c i) c))
- (target . ignore)))
+ (org-export-toc-entry-backend 'html)
info))
(tags (and (eq (plist-get info :with-tags) t)
(org-export-get-tags headline info))))
diff --git a/lisp/ox-md.el b/lisp/ox-md.el
index 8a25f1bd5..121883873 100644
--- a/lisp/ox-md.el
+++ b/lisp/ox-md.el
@@ -582,16 +582,7 @@ contents according to the current headline."
(format "[%s](#%s)"
(org-export-data-with-backend
(org-export-get-alt-title headline info)
- ;; Create an anonymous back-end that will
- ;; ignore any footnote-reference, link,
- ;; radio-target and target in table of
- ;; contents.
- (org-export-create-backend
- :parent 'md
- :transcoders '((footnote-reference . ignore)
- (link . (lambda (object c i) c))
- (radio-target . (lambda (object c i) c))
- (target . ignore)))
+ (org-export-toc-entry-backend 'md)
info)
(or (org-element-property :CUSTOM_ID headline)
(org-export-get-reference headline info))))
diff --git a/lisp/ox-odt.el b/lisp/ox-odt.el
index f00fd99fc..d58563662 100644
--- a/lisp/ox-odt.el
+++ b/lisp/ox-odt.el
@@ -1159,12 +1159,8 @@ table of contents as a string, or nil."
;; Likewise, links, footnote references and regular targets are also
;; suppressed.
(let* ((headlines (org-export-collect-headlines info depth scope))
- (backend (org-export-create-backend
- :parent (org-export-backend-name (plist-get info :back-end))
- :transcoders '((footnote-reference . ignore)
- (link . (lambda (object c i) c))
- (radio-target . (lambda (object c i) c))
- (target . ignore)))))
+ (backend (org-export-toc-entry-backend
+ (org-export-backend-name (plist-get info :back-end)))))
(when headlines
(org-odt--format-toc
(and (not scope) (org-export-translate "Table of Contents" :utf-8 info))
diff --git a/lisp/ox-texinfo.el b/lisp/ox-texinfo.el
index b5903a521..e6aa3e5bc 100644
--- a/lisp/ox-texinfo.el
+++ b/lisp/ox-texinfo.el
@@ -499,8 +499,12 @@ export state, as a plist."
(org-export-create-backend
:parent 'texinfo
:transcoders '((footnote-reference . ignore)
- (link . (lambda (object c i) c))
- (radio-target . (lambda (object c i) c))
+ (link . (lambda (l c i)
+ (or c
+ (org-export-data
+ (org-element-property :raw-link l)
+ i))))
+ (radio-target . (lambda (_r c _i) c))
(target . ignore)))
info))
@@ -519,18 +523,27 @@ strings (e.g., returned by `org-export-get-caption')."
(let* ((backend
(org-export-create-backend
:parent 'texinfo
- :transcoders '((link . (lambda (object c i) c))
- (radio-target . (lambda (object c i) c))
+ :transcoders '((link . (lambda (l c i)
+ (or c
+ (org-export-data
+ (org-element-property :raw-link l)
+ i))))
+ (radio-target . (lambda (_r c _i) c))
(target . ignore))))
(short-backend
(org-export-create-backend
:parent 'texinfo
- :transcoders '((footnote-reference . ignore)
- (inline-src-block . ignore)
- (link . (lambda (object c i) c))
- (radio-target . (lambda (object c i) c))
- (target . ignore)
- (verbatim . ignore))))
+ :transcoders
+ '((footnote-reference . ignore)
+ (inline-src-block . ignore)
+ (link . (lambda (l c i)
+ (or c
+ (org-export-data
+ (org-element-property :raw-link l)
+ i))))
+ (radio-target . (lambda (_r c _i) c))
+ (target . ignore)
+ (verbatim . ignore))))
(short-str
(if (and short caption)
(format "@shortcaption{%s}\n"
diff --git a/lisp/ox.el b/lisp/ox.el
index 1c43577cd..82778747f 100644
--- a/lisp/ox.el
+++ b/lisp/ox.el
@@ -5176,7 +5176,7 @@ return nil."
info 'first-match)))
-;;;; For Tables Of Contents
+;;;; For Tables of Contents
;;
;; `org-export-collect-headlines' builds a list of all exportable
;; headline elements, maybe limited to a certain depth. One can then
@@ -5186,6 +5186,9 @@ return nil."
;; Once the generic function `org-export-collect-elements' is defined,
;; `org-export-collect-tables', `org-export-collect-figures' and
;; `org-export-collect-listings' can be derived from it.
+;;
+;; `org-export-toc-entry-backend' builds a special anonymous back-end
+;; useful to export table of contents' entries.
(defun org-export-collect-headlines (info &optional n scope)
"Collect headlines in order to build a table of contents.
@@ -5271,6 +5274,32 @@ INFO is a plist used as a communication channel.
Return a list of src-block elements with a caption."
(org-export-collect-elements 'src-block info))
+(defun org-export-toc-entry-backend (parent &rest transcoders)
+ "Return an export back-end appropriate for table of contents entries.
+
+PARENT is an export back-end the returned back-end should inherit
+from.
+
+By default, the back-end removes footnote references and targets.
+It also changes links and radio targets into regular text.
+TRANSCODERS optional argument, when non-nil, specifies additional
+transcoders. A transcoder follows the pattern (TYPE . FUNCTION)
+where type is an element or object type and FUNCTION the function
+transcoding it."
+ (declare (indent 1))
+ (org-export-create-backend
+ :parent parent
+ :transcoders
+ (append transcoders
+ `((footnote-reference . ,#'ignore)
+ (link . ,(lambda (l c i)
+ (or c
+ (org-export-data
+ (org-element-property :raw-link l)
+ i))))
+ (radio-target . ,(lambda (_r c _) c))
+ (target . ,#'ignore)))))
+
;;;; Smart Quotes
;;
diff --git a/testing/lisp/test-ox.el b/testing/lisp/test-ox.el
index 72b6c8ccd..6b5e3a30f 100644
--- a/testing/lisp/test-ox.el
+++ b/testing/lisp/test-ox.el
@@ -4320,6 +4320,76 @@ Another text. (ref:text)
(let ((scope (org-element-map tree 'headline #'identity info t)))
(length (org-export-collect-headlines info 1 scope)))))))
+(ert-deftest test-org-export/toc-entry-backend ()
+ "Test `org-export-toc-entry-backend' specifications."
+ ;; Ignore targets.
+ (should
+ (equal "H \n"
+ (org-test-with-temp-text "* H <>"
+ (let (org-export-registered-backends)
+ (org-export-define-backend 'test
+ '((headline . (lambda (h _c i) (org-export-data-with-backend
+ (org-element-property :title h)
+ (org-export-toc-entry-backend 'test)
+ i)))))
+ (org-export-as 'test)))))
+ ;; Ignore footnote references.
+ (should
+ (equal "H \n"
+ (org-test-with-temp-text "[fn:1] Definition\n* H [fn:1]"
+ (let (org-export-registered-backends)
+ (org-export-define-backend 'test
+ '((headline . (lambda (h _c i) (org-export-data-with-backend
+ (org-element-property :title h)
+ (org-export-toc-entry-backend 'test)
+ i)))))
+ (org-export-as 'test)))))
+ ;; Replace plain links with contents, or with path.
+ (should
+ (equal "H Org mode\n"
+ (org-test-with-temp-text "* H [[http://orgmode.org][Org mode]]"
+ (let (org-export-registered-backends)
+ (org-export-define-backend 'test
+ '((headline . (lambda (h _c i) (org-export-data-with-backend
+ (org-element-property :title h)
+ (org-export-toc-entry-backend 'test)
+ i)))))
+ (org-export-as 'test)))))
+ (should
+ (equal "H http://orgmode.org\n"
+ (org-test-with-temp-text "* H [[http://orgmode.org]]"
+ (let (org-export-registered-backends)
+ (org-export-define-backend 'test
+ '((headline . (lambda (h _c i) (org-export-data-with-backend
+ (org-element-property :title h)
+ (org-export-toc-entry-backend 'test)
+ i)))))
+ (org-export-as 'test)))))
+ ;; Replace radio targets with contents.
+ (should
+ (equal "H radio\n"
+ (org-test-with-temp-text "* H <<>>"
+ (let (org-export-registered-backends)
+ (org-export-define-backend 'test
+ '((headline . (lambda (h _c i) (org-export-data-with-backend
+ (org-element-property :title h)
+ (org-export-toc-entry-backend 'test)
+ i)))))
+ (org-export-as 'test)))))
+ ;; With optional argument TRANSCODERS, specify other
+ ;; transformations.
+ (should
+ (equal "H bold\n"
+ (org-test-with-temp-text "* H *bold*"
+ (let (org-export-registered-backends)
+ (org-export-define-backend 'test
+ '((headline . (lambda (h _c i) (org-export-data-with-backend
+ (org-element-property :title h)
+ (org-export-toc-entry-backend 'test
+ '(bold . (lambda (_b c _i) c)))
+ i)))))
+ (org-export-as 'test))))))
+
;;; Templates