* [PATCH] Add :target option for the TOC keyword
@ 2019-05-15 18:54 Sacha Chua
2019-05-21 19:43 ` Nicolas Goaziou
0 siblings, 1 reply; 5+ messages in thread
From: Sacha Chua @ 2019-05-15 18:54 UTC (permalink / raw)
To: emacs-orgmode; +Cc: dc
[-- Attachment #1: Type: text/plain, Size: 241 bytes --]
Hi! Here's a patch to allow table of contents to target a specified
headline. I've added tests for HTML, ASCII, Markdown, and ODT export.
I should have copyright assignment papers already on file.
Does it work for anyone other than me? =)
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Add-target-option-for-the-TOC-keyword.patch --]
[-- Type: text/x-diff, Size: 20592 bytes --]
From 81035b85a10ec62d7a8ddc594349189e97346960 Mon Sep 17 00:00:00 2001
From: Sacha Chua <sacha@sachachua.com>
Date: Wed, 15 May 2019 14:22:05 -0400
Subject: [PATCH 1/1] Add :target option for the TOC keyword
* doc/org-manual.org, etc/ORG_NEWS: Document :target option
for the TOC keyword.
* 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/examples/toc-with-fuzzy-target.org: New example file for
testing target headlines by fuzzy matching.
* testing/examples/toc-with-target.org: New example file for testing
target headlines by CUSTOM_ID.
* testing/lisp/test-ox-html.el: New file.
* testing/lisp/test-ox-md.el: New file.
* testing/lisp/test-ox-odt.el: New file.
* testing/lisp/test-ox.el (test-org-export/collect-headlines): Added
tests for specifying scope by CUSTOM_ID or by fuzzy matching.
* testing/org-test.el (org-test-toc-with-target-file): New.
(org-test-toc-with-fuzzy-target-file): New.
---
doc/org-manual.org | 16 +++++++
etc/ORG-NEWS | 16 +++++++
lisp/ox-ascii.el | 27 +++++++++---
lisp/ox-html.el | 17 +++++++-
lisp/ox-md.el | 27 +++++++++---
lisp/ox-odt.el | 17 +++++++-
testing/examples/toc-with-fuzzy-target.org | 12 +++++
testing/examples/toc-with-target.org | 12 +++++
testing/lisp/test-ox-html.el | 51 ++++++++++++++++++++++
testing/lisp/test-ox-md.el | 51 ++++++++++++++++++++++
testing/lisp/test-ox-odt.el | 51 ++++++++++++++++++++++
testing/lisp/test-ox.el | 50 +++++++++++++++++++++
testing/org-test.el | 6 +++
13 files changed, 335 insertions(+), 18 deletions(-)
create mode 100644 testing/examples/toc-with-fuzzy-target.org
create mode 100644 testing/examples/toc-with-target.org
create mode 100644 testing/lisp/test-ox-html.el
create mode 100644 testing/lisp/test-ox-md.el
create mode 100644 testing/lisp/test-ox-odt.el
diff --git a/doc/org-manual.org b/doc/org-manual.org
index 54b89e5bf..9f3fae308 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -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.
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 541559e64..95358ca7b 100644
--- a/etc/ORG-NEWS
+++ b/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~
diff --git a/lisp/ox-ascii.el b/lisp/ox-ascii.el
index 7917e3dad..969f632b0 100644
--- a/lisp/ox-ascii.el
+++ b/lisp/ox-ascii.el
@@ -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,21 @@ 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
+ ;; link
+ ((string-match ":target +\"\\([^\"]+\\)\"" value)
+ (let ((link (with-temp-buffer
+ (save-excursion
+ (insert (org-make-link-string (match-string 1 value))))
+ (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))
+ (_ nil))))
+ ;; local
+ ((string-match-p "\\<local\\>" value) keyword))))
+ (org-ascii--build-toc info depth keyword scope)))
((string-match-p "\\<tables\\>" value)
(org-ascii--list-tables keyword info))
((string-match-p "\\<listings\\>" value)
diff --git a/lisp/ox-html.el b/lisp/ox-html.el
index e4bd28050..e7059fd35 100644
--- a/lisp/ox-html.el
+++ b/lisp/ox-html.el
@@ -2811,8 +2811,21 @@ 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
+ ;; link
+ ((string-match ":target +\"\\([^\"]+\\)\"" value)
+ (let ((link (with-temp-buffer
+ (save-excursion
+ (insert (org-make-link-string (match-string 1 value))))
+ (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))
+ (_ nil))))
+ ;; local
+ ((string-match-p "\\<local\\>" value) keyword))))
+ (org-html-toc depth info scope)))
((string= "listings" value) (org-html-list-of-listings info))
((string= "tables" value) (org-html-list-of-tables info))))))))
diff --git a/lisp/ox-md.el b/lisp/ox-md.el
index d574e696e..b7addfd54 100644
--- a/lisp/ox-md.el
+++ b/lisp/ox-md.el
@@ -363,9 +363,22 @@ 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
+ ;; link
+ ((string-match ":target +\"\\([^\"]+\\)\"" value)
+ (let ((link (with-temp-buffer
+ (save-excursion
+ (insert (org-make-link-string (match-string 1 value))))
+ (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))
+ (_ nil))))
+ ;; local
+ ((string-match-p "\\<local\\>" value) keyword))))
(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 +563,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 +574,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 +607,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)
diff --git a/lisp/ox-odt.el b/lisp/ox-odt.el
index 497488ef4..5711bbd49 100644
--- a/lisp/ox-odt.el
+++ b/lisp/ox-odt.el
@@ -1991,8 +1991,21 @@ 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
+ ;; link
+ ((string-match ":target +\"\\([^\"]+\\)\"" value)
+ (let ((link (with-temp-buffer
+ (save-excursion
+ (insert (org-make-link-string (match-string 1 value))))
+ (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))
+ (_ nil))))
+ ;; local
+ ((string-match-p "\\<local\\>" value) keyword))))
+ (org-odt-toc depth info scope)))
((string-match-p "tables\\|figures\\|listings" value)
;; FIXME
(ignore))))))))
diff --git a/testing/examples/toc-with-fuzzy-target.org b/testing/examples/toc-with-fuzzy-target.org
new file mode 100644
index 000000000..1e0731c2a
--- /dev/null
+++ b/testing/examples/toc-with-fuzzy-target.org
@@ -0,0 +1,12 @@
+#+OPTIONS: toc:nil
+* Not this section
+** Heading X
+** Heading Y
+* Target
+ :PROPERTIES:
+ :CUSTOM_ID: TargetSection
+ :END:
+** Heading A
+** Heading B
+* Another section
+#+TOC: headlines 1 :target "Target"
diff --git a/testing/examples/toc-with-target.org b/testing/examples/toc-with-target.org
new file mode 100644
index 000000000..5f3fde4c6
--- /dev/null
+++ b/testing/examples/toc-with-target.org
@@ -0,0 +1,12 @@
+#+OPTIONS: toc:nil
+* Not this section
+** Heading X
+** Heading Y
+* Target
+ :PROPERTIES:
+ :CUSTOM_ID: TargetSection
+ :END:
+** Heading A
+** Heading B
+* Another section
+#+TOC: headlines 1 :target "#TargetSection"
diff --git a/testing/lisp/test-ox-html.el b/testing/lisp/test-ox-html.el
new file mode 100644
index 000000000..61c3ea870
--- /dev/null
+++ b/testing/lisp/test-ox-html.el
@@ -0,0 +1,51 @@
+;;; test-ox-html.el --- Tests for ox-html.el -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2019 Sacha Chua
+
+;; Author: Sacha Chua <sacha at sachachua dot com>
+
+;; This file is not part of GNU Emacs.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Code:
+(unless (featurep 'ox)
+ (signal 'missing-test-dependency "org-export"))
+
+(unless (featurep 'ox-html)
+ (signal 'missing-test-dependency "ox-html"))
+
+;;; Table of contents
+
+(ert-deftest test-org-html/keyword ()
+ "Test `org-html-keyword' specifications."
+ (org-test-in-example-file org-test-toc-with-target-file
+ (let* ((body (org-export-as (org-export-get-backend 'html) nil nil t))
+ (toc
+ (and (string-match "text-table-of-contents.*\\(\n.*\\)*</div>" body)
+ (match-string 0 body))))
+ (should (string-match "Heading A" toc))
+ (should (string-match "Heading B" toc))
+ (should-not (string-match "Heading X" toc))))
+ (org-test-in-example-file org-test-toc-with-fuzzy-target-file
+ (let* ((body (org-export-as (org-export-get-backend 'html) nil nil t))
+ (toc
+ (and (string-match "text-table-of-contents.*\\(\n.*\\)*</div>" body)
+ (match-string 0 body))))
+ (should (string-match "Heading A" toc))
+ (should (string-match "Heading B" toc))
+ (should-not (string-match "Heading X" toc)))))
+
+(provide 'test-ox-html)
+;;; test-ox-html.el ends here
diff --git a/testing/lisp/test-ox-md.el b/testing/lisp/test-ox-md.el
new file mode 100644
index 000000000..7ed65b21b
--- /dev/null
+++ b/testing/lisp/test-ox-md.el
@@ -0,0 +1,51 @@
+;;; test-ox-md.el --- Tests for ox-md.el -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2019 Sacha Chua
+
+;; Author: Sacha Chua <sacha at sachachua dot com>
+
+;; This file is not part of GNU Emacs.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+(unless (featurep 'ox)
+ (signal 'missing-test-dependency "org-export"))
+(unless (featurep 'ox-md)
+ (signal 'missing-test-dependency "ox-md"))
+
+;;; Code:
+;;; Table of contents
+(ert-deftest test-org-md/keyword ()
+ "Test `org-md-keyword' specifications."
+ (org-test-in-example-file org-test-toc-with-target-file
+ (let* ((body (org-export-as (org-export-get-backend 'md) nil nil t))
+ (toc
+ (and (string-match "# Another section.*\\(\n.*\\)*" body)
+ (match-string 0 body))))
+ (should (string-match "\\[Heading A" toc))
+ (should (string-match "\\[Heading B" toc))
+ (should-not (string-match "\\[Heading X" toc))))
+ (org-test-in-example-file org-test-toc-with-fuzzy-target-file
+ (let* ((body (org-export-as (org-export-get-backend 'md) nil nil t))
+ (toc
+ (and (string-match "# Another section.*\\(\n.*\\)*" body)
+ (match-string 0 body))))
+ (should (string-match "\\[Heading A" toc))
+ (should (string-match "\\[Heading B" toc))
+ (should-not (string-match "\\[Heading X" toc)))))
+
+(provide 'test-ox-md)
+
+;;; test-ox-md.el ends here
+
diff --git a/testing/lisp/test-ox-odt.el b/testing/lisp/test-ox-odt.el
new file mode 100644
index 000000000..15788b0ae
--- /dev/null
+++ b/testing/lisp/test-ox-odt.el
@@ -0,0 +1,51 @@
+;;; test-ox-odt.el --- Tests for ox-md.el -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2019 Sacha Chua
+
+;; Author: Sacha Chua <sacha at sachachua dot com>
+
+;; This file is not part of GNU Emacs.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+(unless (featurep 'ox)
+ (signal 'missing-test-dependency "org-export"))
+(unless (featurep 'ox-odt)
+ (signal 'missing-test-dependency "ox-odt"))
+
+;;; Code:
+;;; Table of contents
+(ert-deftest test-org-odt/keyword ()
+ "Test `org-odt-keyword' specifications."
+ (org-test-in-example-file org-test-toc-with-target-file
+ (let* ((body (org-export-as (org-export-get-backend 'odt) nil nil t))
+ (toc
+ (and (string-match "<text:index-body>.*\\(\n.*\\)*" body)
+ (match-string 0 body))))
+ (should (string-match "Heading A" toc))
+ (should (string-match "Heading B" toc))
+ (should-not (string-match "Heading X" toc))))
+ (org-test-in-example-file org-test-toc-with-fuzzy-target-file
+ (let* ((body (org-export-as (org-export-get-backend 'odt) nil nil t))
+ (toc
+ (and (string-match "<text:index-body>.*\\(\n.*\\)*" body)
+ (match-string 0 body))))
+ (should (string-match "Heading A" toc))
+ (should (string-match "Heading B" toc))
+ (should-not (string-match "Heading X" toc)))))
+
+(provide 'test-ox-odt)
+
+;;; test-ox-odt.el ends here
+
diff --git a/testing/lisp/test-ox.el b/testing/lisp/test-ox.el
index 43637b926..36740364f 100644
--- a/testing/lisp/test-ox.el
+++ b/testing/lisp/test-ox.el
@@ -4615,6 +4615,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")
diff --git a/testing/org-test.el b/testing/org-test.el
index 39c346410..4e8794348 100644
--- a/testing/org-test.el
+++ b/testing/org-test.el
@@ -90,6 +90,12 @@ org-test searches this directory up the directory tree.")
(defconst org-test-link-in-heading-file
(expand-file-name "link-in-heading.org" org-test-dir))
+(defconst org-test-toc-with-target-file
+ (expand-file-name "toc-with-target.org" org-test-example-dir))
+
+(defconst org-test-toc-with-fuzzy-target-file
+ (expand-file-name "toc-with-fuzzy-target.org" org-test-example-dir))
+
(defconst org-id-locations-file
(expand-file-name ".test-org-id-locations" org-test-dir))
--
2.17.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH] Add :target option for the TOC keyword
2019-05-15 18:54 [PATCH] Add :target option for the TOC keyword Sacha Chua
@ 2019-05-21 19:43 ` Nicolas Goaziou
2019-05-29 15:40 ` Sacha Chua
0 siblings, 1 reply; 5+ messages in thread
From: Nicolas Goaziou @ 2019-05-21 19:43 UTC (permalink / raw)
To: Sacha Chua; +Cc: dc, emacs-orgmode
Hello,
Sacha Chua <sacha@sachachua.com> writes:
> Hi! Here's a patch to allow table of contents to target a specified
> headline. I've added tests for HTML, ASCII, Markdown, and ODT export.
> I should have copyright assignment papers already on file.
>
> Does it work for anyone other than me? =)
Thank you! I cannot guess the use case, but you certainly have one :)
Even if this doesn't sound like a very common need, it doesn't add too
much overhead to the code base either. It would be nice if it really
generalized "local" keyword, i.e., with ":target local", "local" variant
being supported but not documented.
Also, there seems to be much code duplication in the export back-ends.
Would it make sense to factor this out into a function in "ox.el". For
example, `org-export-resolve-link-string' or some such? With some tests
for bonus points.
Speaking of tests, we usually don't write tests for export back-ends, so
you can drop them.
Could you send an updated patch?
Regards,
--
Nicolas Goaziou
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] Add :target option for the TOC keyword
2019-05-21 19:43 ` Nicolas Goaziou
@ 2019-05-29 15:40 ` Sacha Chua
2019-05-31 16:30 ` Nicolas Goaziou
0 siblings, 1 reply; 5+ messages in thread
From: Sacha Chua @ 2019-05-29 15:40 UTC (permalink / raw)
To: emacs-orgmode
[-- Attachment #1: Type: text/plain, Size: 1463 bytes --]
Nicolas Goaziou <mail@nicolasgoaziou.fr> writes:
Hello, Nicolas!
> Thank you! I cannot guess the use case, but you certainly have one :)
I'm guessing DC Toedt likes to make Org files that have some kind of
overview TOC in a different section. I haven't used it myself, but it
seems like a potentially handy thing to have.
> Even if this doesn't sound like a very common need, it doesn't add too
> much overhead to the code base either. It would be nice if it really
> generalized "local" keyword, i.e., with ":target local", "local" variant
> being supported but not documented.
The old way just did a simple regular expression search for local, so
:target local actually already works. :target "local" tries to do a
fuzzy search for a headline with "local" text in it, though. Is that
what you mean? I'm not sure if the best approach would be to:
- catch the signal raised by org-export-resolve-fuzzy-link and then check for the
special case of "local"
- always handle :target "local" as a local TOC
- or just leave the behaviour as it is. What would you recommend?
> Also, there seems to be much code duplication in the export back-ends.
> Would it make sense to factor this out into a function in "ox.el". For
> example, `org-export-resolve-link-string' or some such? With some tests
> for bonus points.
I made an org-export-resolve-link that can take string or link objects.
It doesn't handle all the potential link types, though, just id and
fuzzy.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Add-target-option-for-the-TOC-keyword.patch --]
[-- Type: text/x-diff, Size: 12959 bytes --]
From be360242b00a079afb110b019c6dd9ff6ebc36c3 Mon Sep 17 00:00:00 2001
From: Sacha Chua <sacha@sachachua.com>
Date: Wed, 15 May 2019 14:22:05 -0400
Subject: [PATCH] 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.
---
doc/org-manual.org | 16 ++++++++
etc/ORG-NEWS | 16 ++++++++
lisp/ox-ascii.el | 20 ++++++----
lisp/ox-html.el | 10 ++++-
lisp/ox-md.el | 20 ++++++----
lisp/ox-odt.el | 10 ++++-
lisp/ox.el | 28 ++++++++++++++
testing/lisp/test-ox.el | 84 +++++++++++++++++++++++++++++++++++++++++
8 files changed, 186 insertions(+), 18 deletions(-)
diff --git a/doc/org-manual.org b/doc/org-manual.org
index 54b89e5bf..9f3fae308 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -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.
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 541559e64..95358ca7b 100644
--- a/etc/ORG-NEWS
+++ b/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~
diff --git a/lisp/ox-ascii.el b/lisp/ox-ascii.el
index 7917e3dad..20327169b 100644
--- a/lisp/ox-ascii.el
+++ b/lisp/ox-ascii.el
@@ -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,14 @@ 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
+ ;; link
+ ((string-match ":target +\"\\([^\"]+\\)\"" value)
+ (org-export-resolve-link (match-string 1 value) info))
+ ;; local
+ ((string-match-p "\\<local\\>" value) keyword))))
+ (org-ascii--build-toc info depth keyword scope)))
((string-match-p "\\<tables\\>" value)
(org-ascii--list-tables keyword info))
((string-match-p "\\<listings\\>" value)
diff --git a/lisp/ox-html.el b/lisp/ox-html.el
index e4bd28050..86ee07b8d 100644
--- a/lisp/ox-html.el
+++ b/lisp/ox-html.el
@@ -2811,8 +2811,14 @@ 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
+ ;; link
+ ((string-match ":target +\"\\([^\"]+\\)\"" value)
+ (org-export-resolve-link (match-string 1 value) info))
+ ;; local
+ ((string-match-p "\\<local\\>" value) keyword))))
+ (org-html-toc depth info scope)))
((string= "listings" value) (org-html-list-of-listings info))
((string= "tables" value) (org-html-list-of-tables info))))))))
diff --git a/lisp/ox-md.el b/lisp/ox-md.el
index d574e696e..566705ca0 100644
--- a/lisp/ox-md.el
+++ b/lisp/ox-md.el
@@ -363,9 +363,15 @@ 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
+ ;; link
+ ((string-match ":target +\"\\([^\"]+\\)\"" value)
+ (org-export-resolve-link (match-string 1 value) info))
+ ;; local
+ ((string-match-p "\\<local\\>" value) keyword))))
(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 +556,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 +567,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 +600,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)
diff --git a/lisp/ox-odt.el b/lisp/ox-odt.el
index 497488ef4..e630fe3a8 100644
--- a/lisp/ox-odt.el
+++ b/lisp/ox-odt.el
@@ -1991,8 +1991,14 @@ 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
+ ;; link
+ ((string-match ":target +\"\\([^\"]+\\)\"" value)
+ (org-export-resolve-link (match-string 1 value) info))
+ ;; local
+ ((string-match-p "\\<local\\>" value) keyword))))
+ (org-odt-toc depth info scope)))
((string-match-p "tables\\|figures\\|listings" value)
;; FIXME
(ignore))))))))
diff --git a/lisp/ox.el b/lisp/ox.el
index f0ecd8978..e989b37c6 100644
--- a/lisp/ox.el
+++ b/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 that element.
+
+- If LINK path matches a fuzzy link, return that element.
+
+- 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))
diff --git a/testing/lisp/test-ox.el b/testing/lisp/test-ox.el
index 43637b926..fc5f857b2 100644
--- a/testing/lisp/test-ox.el
+++ b/testing/lisp/test-ox.el
@@ -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
@@ -4615,6 +4649,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")
--
2.17.1
[-- Attachment #3: Type: text/plain, Size: 7 bytes --]
Sacha
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH] Add :target option for the TOC keyword
2019-05-29 15:40 ` Sacha Chua
@ 2019-05-31 16:30 ` Nicolas Goaziou
2019-06-05 19:20 ` Sacha Chua
0 siblings, 1 reply; 5+ messages in thread
From: Nicolas Goaziou @ 2019-05-31 16:30 UTC (permalink / raw)
To: Sacha Chua; +Cc: emacs-orgmode
Hello,
Sacha Chua <sacha@sachachua.com> writes:
> - catch the signal raised by org-export-resolve-fuzzy-link and then check for the
> special case of "local"
> - always handle :target "local" as a local TOC
> - or just leave the behaviour as it is. What would you recommend?
The latter is fine.
I applied you patch. Thank you.
Regards,
--
Nicolas Goaziou
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] Add :target option for the TOC keyword
2019-05-31 16:30 ` Nicolas Goaziou
@ 2019-06-05 19:20 ` Sacha Chua
0 siblings, 0 replies; 5+ messages in thread
From: Sacha Chua @ 2019-06-05 19:20 UTC (permalink / raw)
To: Sacha Chua, emacs list
[-- Attachment #1.1: Type: text/plain, Size: 687 bytes --]
Whoops, I'm so sorry - I accidentally used org-make-link-string instead of
org-link-make-string. Tiny patch fixing it attached.
Sacha Chua - sacha@sachachua.com - Blog: sachachua.com - Mobile: +1 416 823
2669
On Fri, May 31, 2019 at 12:30 PM Nicolas Goaziou <mail@nicolasgoaziou.fr>
wrote:
> Hello,
>
> Sacha Chua <sacha@sachachua.com> writes:
>
> > - catch the signal raised by org-export-resolve-fuzzy-link and then
> check for the
> > special case of "local"
> > - always handle :target "local" as a local TOC
> > - or just leave the behaviour as it is. What would you recommend?
>
> The latter is fine.
>
> I applied you patch. Thank you.
>
> Regards,
>
> --
> Nicolas Goaziou
>
[-- Attachment #1.2: Type: text/html, Size: 1521 bytes --]
[-- Attachment #2: 0001-org-export-resolve-link-Use-org-link-make-string.patch --]
[-- Type: text/x-patch, Size: 881 bytes --]
From 265886aedf202cce3cc7ab4f88bab318dd69ca86 Mon Sep 17 00:00:00 2001
From: Sacha Chua <sacha@sachachua.com>
Date: Wed, 5 Jun 2019 15:17:57 -0400
Subject: [PATCH] org-export-resolve-link: Use org-link-make-string
* lisp/ox.el (org-export-resolve-link): Use org-link-make-string
instead of org-make-link-string.
---
lisp/ox.el | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lisp/ox.el b/lisp/ox.el
index e989b37c6..05c8daebc 100644
--- a/lisp/ox.el
+++ b/lisp/ox.el
@@ -4478,7 +4478,7 @@ Return value can be an object or an element:
(when (stringp link)
(setq link (with-temp-buffer
(save-excursion
- (insert (org-make-link-string link)))
+ (insert (org-link-make-string link)))
(org-element-link-parser))))
(pcase (org-element-property :type link)
((or "custom-id" "id") (org-export-resolve-id-link link info))
--
2.17.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2019-06-05 19:20 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2019-05-15 18:54 [PATCH] Add :target option for the TOC keyword Sacha Chua
2019-05-21 19:43 ` Nicolas Goaziou
2019-05-29 15:40 ` Sacha Chua
2019-05-31 16:30 ` Nicolas Goaziou
2019-06-05 19:20 ` Sacha Chua
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).