mirror of
https://git.savannah.gnu.org/git/emacs/org-mode.git
synced 2024-11-23 07:18:53 +00:00
Add :target option for the TOC keyword
* doc/org-manual.org, etc/ORG_NEWS: Document :target option for the TOC keyword. * lisp/ox.el (org-export-resolve-link): New function. * lisp/ox-ascii.el (org-ascii-keyword): Added :target to the TOC keyword. (org-ascii--build-toc): Changed LOCAL argument to SCOPE. * lisp/ox-html.el (org-html-keyword): Added :target to the TOC keyword. * lisp/ox-md.el (org-md-keyword): Added :target to the TOC keyword. (org-md--build-toc): Changed LOCAL argument to SCOPE. * lisp/ox-odt.el (org-odt-keyword): Added :target to the TOC keyword. * testing/lisp/test-ox.el (test-org-export/collect-headlines): Added tests for specifying scope by CUSTOM_ID or by fuzzy matching. (test-org-export/resolve-link): New test.
This commit is contained in:
parent
99aa99426d
commit
a41e9950ae
@ -11551,6 +11551,22 @@ file requires the inclusion of the titletoc package. Because of
|
||||
compatibility issues, titletoc has to be loaded /before/ hyperref.
|
||||
Customize the ~org-latex-default-packages-alist~ variable.
|
||||
|
||||
The following example inserts a table of contents that links to the
|
||||
children of the specified target.
|
||||
|
||||
#+begin_example
|
||||
,* Target
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: TargetSection
|
||||
:END:
|
||||
,** Heading A
|
||||
,** Heading B
|
||||
,* Another section
|
||||
,#+TOC: headlines 1 :target #TargetSection
|
||||
#+end_example
|
||||
|
||||
The =:target= attribute is supported in HTML, Markdown, ODT, and ASCII export.
|
||||
|
||||
Use the =TOC= keyword to generate list of tables---respectively, all
|
||||
listings---with captions.
|
||||
|
||||
|
16
etc/ORG-NEWS
16
etc/ORG-NEWS
@ -212,6 +212,22 @@ This attribute overrides the =:width= and =:height= attributes.
|
||||
[[https://orgmode.org/img/org-mode-unicorn-logo.png]]
|
||||
#+end_example
|
||||
|
||||
*** Allow specifying the target for a table of contents
|
||||
|
||||
The =+TOC= keyword now accepts a =:target:= attribute that specifies
|
||||
the headline to use for making the table of contents.
|
||||
|
||||
#+begin_example
|
||||
,* Target
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: TargetSection
|
||||
:END:
|
||||
,** Heading A
|
||||
,** Heading B
|
||||
,* Another section
|
||||
,#+TOC: headlines 1 :target "#TargetSection"
|
||||
#+end_example
|
||||
|
||||
** New functions
|
||||
*** ~org-dynamic-block-insert-dblock~
|
||||
|
||||
|
@ -731,7 +731,7 @@ caption keyword."
|
||||
(org-export-data caption info))
|
||||
(org-ascii--current-text-width element info) info)))))
|
||||
|
||||
(defun org-ascii--build-toc (info &optional n keyword local)
|
||||
(defun org-ascii--build-toc (info &optional n keyword scope)
|
||||
"Return a table of contents.
|
||||
|
||||
INFO is a plist used as a communication channel.
|
||||
@ -742,10 +742,10 @@ depth of the table.
|
||||
Optional argument KEYWORD specifies the TOC keyword, if any, from
|
||||
which the table of contents generation has been initiated.
|
||||
|
||||
When optional argument LOCAL is non-nil, build a table of
|
||||
contents according to the current headline."
|
||||
When optional argument SCOPE is non-nil, build a table of
|
||||
contents according to the specified scope."
|
||||
(concat
|
||||
(unless local
|
||||
(unless scope
|
||||
(let ((title (org-ascii--translate "Table of Contents" info)))
|
||||
(concat title "\n"
|
||||
(make-string
|
||||
@ -767,7 +767,7 @@ contents according to the current headline."
|
||||
(or (not (plist-get info :with-tags))
|
||||
(eq (plist-get info :with-tags) 'not-in-toc))
|
||||
'toc))))
|
||||
(org-export-collect-headlines info n (and local keyword)) "\n"))))
|
||||
(org-export-collect-headlines info n scope) "\n"))))
|
||||
|
||||
(defun org-ascii--list-listings (keyword info)
|
||||
"Return a list of listings.
|
||||
@ -1516,8 +1516,13 @@ information."
|
||||
((string-match-p "\\<headlines\\>" value)
|
||||
(let ((depth (and (string-match "\\<[0-9]+\\>" value)
|
||||
(string-to-number (match-string 0 value))))
|
||||
(localp (string-match-p "\\<local\\>" value)))
|
||||
(org-ascii--build-toc info depth keyword localp)))
|
||||
(scope
|
||||
(cond
|
||||
((string-match ":target +\\(\".+?\"\\|\\S-+\\)" value) ;link
|
||||
(org-export-resolve-link
|
||||
(org-strip-quotes (match-string 1 value)) info))
|
||||
((string-match-p "\\<local\\>" value) keyword)))) ;local
|
||||
(org-ascii--build-toc info depth keyword scope)))
|
||||
((string-match-p "\\<tables\\>" value)
|
||||
(org-ascii--list-tables keyword info))
|
||||
((string-match-p "\\<listings\\>" value)
|
||||
|
@ -2813,8 +2813,13 @@ CONTENTS is nil. INFO is a plist holding contextual information."
|
||||
((string-match "\\<headlines\\>" value)
|
||||
(let ((depth (and (string-match "\\<[0-9]+\\>" value)
|
||||
(string-to-number (match-string 0 value))))
|
||||
(localp (string-match-p "\\<local\\>" value)))
|
||||
(org-html-toc depth info (and localp keyword))))
|
||||
(scope
|
||||
(cond
|
||||
((string-match ":target +\\(\".+?\"\\|\\S-+\\)" value) ;link
|
||||
(org-export-resolve-link
|
||||
(org-strip-quotes (match-string 1 value)) info))
|
||||
((string-match-p "\\<local\\>" value) keyword)))) ;local
|
||||
(org-html-toc depth info scope)))
|
||||
((string= "listings" value) (org-html-list-of-listings info))
|
||||
((string= "tables" value) (org-html-list-of-tables info))))))))
|
||||
|
||||
|
@ -363,9 +363,14 @@ channel."
|
||||
((string-match-p "\\<headlines\\>" value)
|
||||
(let ((depth (and (string-match "\\<[0-9]+\\>" value)
|
||||
(string-to-number (match-string 0 value))))
|
||||
(local? (string-match-p "\\<local\\>" value)))
|
||||
(scope
|
||||
(cond
|
||||
((string-match ":target +\\(\".+?\"\\|\\S-+\\)" value) ;link
|
||||
(org-export-resolve-link
|
||||
(org-strip-quotes (match-string 1 value)) info))
|
||||
((string-match-p "\\<local\\>" value) keyword)))) ;local
|
||||
(org-remove-indentation
|
||||
(org-md--build-toc info depth keyword local?)))))))
|
||||
(org-md--build-toc info depth keyword scope)))))))
|
||||
(_ (org-export-with-backend 'html keyword contents info))))
|
||||
|
||||
|
||||
@ -550,7 +555,7 @@ a communication channel."
|
||||
|
||||
;;;; Template
|
||||
|
||||
(defun org-md--build-toc (info &optional n keyword local)
|
||||
(defun org-md--build-toc (info &optional n keyword scope)
|
||||
"Return a table of contents.
|
||||
|
||||
INFO is a plist used as a communication channel.
|
||||
@ -561,10 +566,10 @@ depth of the table.
|
||||
Optional argument KEYWORD specifies the TOC keyword, if any, from
|
||||
which the table of contents generation has been initiated.
|
||||
|
||||
When optional argument LOCAL is non-nil, build a table of
|
||||
contents according to the current headline."
|
||||
When optional argument SCOPE is non-nil, build a table of
|
||||
contents according to the specified element."
|
||||
(concat
|
||||
(unless local
|
||||
(unless scope
|
||||
(let ((style (plist-get info :md-headline-style))
|
||||
(title (org-html--translate "Table of Contents" info)))
|
||||
(org-md--headline-title style 1 title nil)))
|
||||
@ -594,7 +599,7 @@ contents according to the current headline."
|
||||
(org-make-tag-string
|
||||
(org-export-get-tags headline info)))))
|
||||
(concat indentation bullet title tags)))
|
||||
(org-export-collect-headlines info n (and local keyword)) "\n")
|
||||
(org-export-collect-headlines info n scope) "\n")
|
||||
"\n"))
|
||||
|
||||
(defun org-md--footnote-formatted (footnote info)
|
||||
|
@ -1991,8 +1991,13 @@ information."
|
||||
(let ((depth (or (and (string-match "\\<[0-9]+\\>" value)
|
||||
(string-to-number (match-string 0 value)))
|
||||
(plist-get info :headline-levels)))
|
||||
(localp (string-match-p "\\<local\\>" value)))
|
||||
(org-odt-toc depth info (and localp keyword))))
|
||||
(scope
|
||||
(cond
|
||||
((string-match ":target +\\(\".+?\"\\|\\S-+\\)" value) ;link
|
||||
(org-export-resolve-link
|
||||
(org-strip-quotes (match-string 1 value)) info))
|
||||
((string-match-p "\\<local\\>" value) keyword)))) ;local
|
||||
(org-odt-toc depth info scope)))
|
||||
((string-match-p "tables\\|figures\\|listings" value)
|
||||
;; FIXME
|
||||
(ignore))))))))
|
||||
|
28
lisp/ox.el
28
lisp/ox.el
@ -4171,6 +4171,9 @@ meant to be translated with `org-export-data' or alike."
|
||||
;; specified id or custom-id in parse tree, the path to the external
|
||||
;; file with the id.
|
||||
;;
|
||||
;; `org-export-resolve-link' searches for the destination of a link
|
||||
;; within the parsed tree and returns the element.
|
||||
;;
|
||||
;; `org-export-resolve-coderef' associates a reference to a line
|
||||
;; number in the element it belongs, or returns the reference itself
|
||||
;; when the element isn't numbered.
|
||||
@ -4457,6 +4460,31 @@ has type \"radio\"."
|
||||
radio))
|
||||
info 'first-match)))
|
||||
|
||||
(defun org-export-resolve-link (link info)
|
||||
"Return LINK destination.
|
||||
|
||||
LINK is a string or a link object.
|
||||
|
||||
INFO is a plist holding contextual information.
|
||||
|
||||
Return value can be an object or an element:
|
||||
|
||||
- If LINK path matches an ID or a custom ID, return the headline.
|
||||
|
||||
- If LINK path matches a fuzzy link, return its destination.
|
||||
|
||||
- Otherwise, throw an error."
|
||||
;; Convert string links to link objects.
|
||||
(when (stringp link)
|
||||
(setq link (with-temp-buffer
|
||||
(save-excursion
|
||||
(insert (org-make-link-string link)))
|
||||
(org-element-link-parser))))
|
||||
(pcase (org-element-property :type link)
|
||||
((or "custom-id" "id") (org-export-resolve-id-link link info))
|
||||
("fuzzy" (org-export-resolve-fuzzy-link link info))
|
||||
(_ (signal 'org-link-broken (list (org-element-property :path link))))))
|
||||
|
||||
(defun org-export-file-uri (filename)
|
||||
"Return file URI associated to FILENAME."
|
||||
(cond ((string-prefix-p "//" filename) (concat "file:" filename))
|
||||
|
@ -3197,6 +3197,40 @@ Paragraph[fn:1][fn:2][fn:lbl3:C<<target>>][[test]][[target]]
|
||||
(lambda (link) (org-export-resolve-fuzzy-link link info))
|
||||
info t))))
|
||||
|
||||
(ert-deftest test-org-export/resolve-link ()
|
||||
"Test `org-export-resolve-link' specifications."
|
||||
(should
|
||||
;; Match ID links
|
||||
(equal
|
||||
"Headline1"
|
||||
(org-test-with-parsed-data "* Headline1
|
||||
:PROPERTIES:
|
||||
:ID: aaaa
|
||||
:END:
|
||||
* Headline2"
|
||||
(org-element-property
|
||||
:raw-value (org-export-resolve-link "#aaaa" info)))))
|
||||
;; Match Custom ID links
|
||||
(should
|
||||
(equal
|
||||
"Headline1"
|
||||
(org-test-with-parsed-data
|
||||
"* Headline1
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: test
|
||||
:END:
|
||||
* Headline2"
|
||||
(org-element-property
|
||||
:raw-value (org-export-resolve-link "#test" info)))))
|
||||
;; Match fuzzy links
|
||||
(should
|
||||
(equal
|
||||
"B"
|
||||
(org-test-with-parsed-data
|
||||
"* A\n* B\n* C"
|
||||
(org-element-property
|
||||
:raw-value (org-export-resolve-link "B" info))))))
|
||||
|
||||
(defun test-org-gen-loc-list(text type)
|
||||
(org-test-with-parsed-data text
|
||||
(org-element-map tree type
|
||||
@ -4610,6 +4644,56 @@ Another text. (ref:text)
|
||||
(let ((scope (org-element-map tree 'headline #'identity info t)))
|
||||
(mapcar (lambda (h) (org-element-property :raw-value h))
|
||||
(org-export-collect-headlines info nil scope))))))
|
||||
;; Collect headlines from a scope specified by a fuzzy match
|
||||
(should
|
||||
(equal '("H3" "H4")
|
||||
(org-test-with-parsed-data "* HA
|
||||
** H1
|
||||
** H2
|
||||
* Target
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: TargetSection
|
||||
:END:
|
||||
** H3
|
||||
** H4
|
||||
* HB
|
||||
** H5
|
||||
"
|
||||
(mapcar
|
||||
(lambda (h) (org-element-property :raw-value h))
|
||||
(org-export-collect-headlines
|
||||
info
|
||||
nil
|
||||
(org-export-resolve-fuzzy-link
|
||||
(with-temp-buffer
|
||||
(save-excursion (insert "[[Target]]"))
|
||||
(org-element-link-parser))
|
||||
info))))))
|
||||
;; Collect headlines from a scope specified by CUSTOM_ID
|
||||
(should
|
||||
(equal '("H3" "H4")
|
||||
(org-test-with-parsed-data "* Not this section
|
||||
** H1
|
||||
** H2
|
||||
* Target
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: TargetSection
|
||||
:END:
|
||||
** H3
|
||||
** H4
|
||||
* Another
|
||||
** H5
|
||||
"
|
||||
(mapcar
|
||||
(lambda (h) (org-element-property :raw-value h))
|
||||
(org-export-collect-headlines
|
||||
info
|
||||
nil
|
||||
(org-export-resolve-id-link
|
||||
(with-temp-buffer
|
||||
(save-excursion (insert "[[#TargetSection]]"))
|
||||
(org-element-link-parser))
|
||||
info))))))
|
||||
;; When collecting locally, optional level is relative.
|
||||
(should
|
||||
(equal '("H2")
|
||||
|
Loading…
Reference in New Issue
Block a user