Browse Source

ox: Implement local table of contents

* lisp/ox.el (org-export-collect-headlines): Allow to collect
  headlines locally.
* testing/lisp/test-ox.el (test-org-export/collect-headlines): Add
  tests.

* lisp/ox-ascii.el (org-ascii--build-toc):
(org-ascii-keyword):
* lisp/ox-html.el (org-html-toc):
(org-html-keyword):
* lisp/ox-odt.el (org-odt-toc): Allow local table of contents.
(org-odt--format-toc): New function.
(org-odt-begin-toc, org-odt-end-toc): Remove functions.

* lisp/ox-latex.el (org-latex-logfiles-extensions): Optionally remove
  "ptc" files.
(org-latex-headline, org-latex-keyword): Implement partial table of
contents assuming "titletoc" package is loaded.

* etc/ORG-NEWS:
* doc/org.texi (Table of contents): Document new parameter.
Nicolas Goaziou 10 years ago
parent
commit
b07e2f6ff1
8 changed files with 207 additions and 132 deletions
  1. 12 3
      doc/org.texi
  2. 3 0
      etc/ORG-NEWS
  3. 40 37
      lisp/ox-ascii.el
  4. 27 24
      lisp/ox-html.el
  5. 41 14
      lisp/ox-latex.el
  6. 49 44
      lisp/ox-odt.el
  7. 22 9
      lisp/ox.el
  8. 13 1
      testing/lisp/test-ox.el

+ 12 - 3
doc/org.texi

@@ -9700,9 +9700,18 @@ location(s).
 #+TOC: headlines 2        (insert TOC here, with two headline levels)
 #+TOC: headlines 2        (insert TOC here, with two headline levels)
 @end example
 @end example
 
 
-Multiple @code{#+TOC: headline} lines are allowed.  The same @code{TOC}
-keyword can also generate a list of all tables (resp.@: all listings) with a
-caption in the buffer.
+Moreover, if you append @samp{local} parameter, the table contains only
+entries for current section's children@footnote{For @LaTeX{} export, this
+feature requires ``titletoc'' package.}.  In this case, any depth parameter
+becomes relative to the current level.
+
+@example
+* Section
+#+TOC: headlines 1 local  (insert local TOC, with direct children only)
+@end example
+
+The same @code{TOC} keyword can also generate a list of all tables (resp.@:
+all listings) with a caption in the document.
 
 
 @example
 @example
 #+TOC: listings           (build a list of listings)
 #+TOC: listings           (build a list of listings)

+ 3 - 0
etc/ORG-NEWS

@@ -180,6 +180,9 @@ property is inherited by children.
 It is now possible to specify a function, both programatically,
 It is now possible to specify a function, both programatically,
 through a new optional argument, and interactively with ~f~ or ~F~
 through a new optional argument, and interactively with ~f~ or ~F~
 keys, to sort a table.
 keys, to sort a table.
+*** Table of contents can be local to a section
+The ~TOC~ keywords now accepts an optional ~local~ parameter.  See
+manual for details.
 *** Countdown timers can now be paused.
 *** Countdown timers can now be paused.
 ~org-timer-pause-time~ wil now pause and restart both relative and
 ~org-timer-pause-time~ wil now pause and restart both relative and
 countdown timers.
 countdown timers.

+ 40 - 37
lisp/ox-ascii.el

@@ -744,7 +744,7 @@ caption keyword."
 		 (org-export-data caption info))
 		 (org-export-data caption info))
 	 (org-ascii--current-text-width element info) info)))))
 	 (org-ascii--current-text-width element info) info)))))
 
 
-(defun org-ascii--build-toc (info &optional n keyword)
+(defun org-ascii--build-toc (info &optional n keyword local)
   "Return a table of contents.
   "Return a table of contents.
 
 
 INFO is a plist used as a communication channel.
 INFO is a plist used as a communication channel.
@@ -753,29 +753,34 @@ Optional argument N, when non-nil, is an integer specifying the
 depth of the table.
 depth of the table.
 
 
 Optional argument KEYWORD specifies the TOC keyword, if any, from
 Optional argument KEYWORD specifies the TOC keyword, if any, from
-which the table of contents generation has been initiated."
-  (let ((title (org-ascii--translate "Table of Contents" info)))
-    (concat
-     title "\n"
-     (make-string (string-width title)
-		  (if (eq (plist-get info :ascii-charset) 'utf-8) ?─ ?_))
-     "\n\n"
-     (let ((text-width
-	    (if keyword (org-ascii--current-text-width keyword info)
-	      (- (plist-get info :ascii-text-width)
-		 (plist-get info :ascii-global-margin)))))
-       (mapconcat
-	(lambda (headline)
-	  (let* ((level (org-export-get-relative-level headline info))
-		 (indent (* (1- level) 3)))
-	    (concat
-	     (unless (zerop indent) (concat (make-string (1- indent) ?.) " "))
-	     (org-ascii--build-title
-	      headline info (- text-width indent) nil
-	      (or (not (plist-get info :with-tags))
-		  (eq (plist-get info :with-tags) 'not-in-toc))
-	      'toc))))
-	(org-export-collect-headlines info n) "\n")))))
+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."
+  (concat
+   (unless local
+     (let ((title (org-ascii--translate "Table of Contents" info)))
+       (concat title "\n"
+	       (make-string
+		(string-width title)
+		(if (eq (plist-get info :ascii-charset) 'utf-8) ?─ ?_))
+	       "\n\n")))
+   (let ((text-width
+	  (if keyword (org-ascii--current-text-width keyword info)
+	    (- (plist-get info :ascii-text-width)
+	       (plist-get info :ascii-global-margin)))))
+     (mapconcat
+      (lambda (headline)
+	(let* ((level (org-export-get-relative-level headline info))
+	       (indent (* (1- level) 3)))
+	  (concat
+	   (unless (zerop indent) (concat (make-string (1- indent) ?.) " "))
+	   (org-ascii--build-title
+	    headline info (- text-width indent) nil
+	    (or (not (plist-get info :with-tags))
+		(eq (plist-get info :with-tags) 'not-in-toc))
+	    'toc))))
+      (org-export-collect-headlines info n keyword) "\n"))))
 
 
 (defun org-ascii--list-listings (keyword info)
 (defun org-ascii--list-listings (keyword info)
   "Return a list of listings.
   "Return a list of listings.
@@ -1459,24 +1464,22 @@ contextual information."
   "Transcode a KEYWORD element from Org to ASCII.
   "Transcode a KEYWORD element from Org to ASCII.
 CONTENTS is nil.  INFO is a plist holding contextual
 CONTENTS is nil.  INFO is a plist holding contextual
 information."
 information."
-  (let ((key (org-element-property :key keyword)))
+  (let ((key (org-element-property :key keyword))
+	(value (org-element-property :value keyword)))
     (cond
     (cond
-     ((string= key "ASCII")
-      (org-ascii--justify-element
-       (org-element-property :value keyword) keyword info))
+     ((string= key "ASCII") (org-ascii--justify-element value keyword info))
      ((string= key "TOC")
      ((string= key "TOC")
       (org-ascii--justify-element
       (org-ascii--justify-element
-       (let ((value (downcase (org-element-property :value keyword))))
+       (let ((case-fold-search t))
 	 (cond
 	 (cond
-	  ((string-match "\\<headlines\\>" value)
-	   (let ((depth (or (and (string-match "[0-9]+" value)
-				 (string-to-number (match-string 0 value)))
-			    (plist-get info :with-toc))))
-	     (org-ascii--build-toc
-	      info (and (wholenump depth) depth) keyword)))
-	  ((string= "tables" value)
+	  ((org-string-match-p "\\<headlines\\>" value)
+	   (let ((depth (and (string-match "\\<[0-9]+\\>" value)
+			     (string-to-number (match-string 0 value))))
+		 (localp (org-string-match-p "\\<local\\>" value)))
+	     (org-ascii--build-toc info depth keyword localp)))
+	  ((org-string-match-p "\\<tables\\>" value)
 	   (org-ascii--list-tables keyword info))
 	   (org-ascii--list-tables keyword info))
-	  ((string= "listings" value)
+	  ((org-string-match-p "\\<listings\\>" value)
 	   (org-ascii--list-listings keyword info))))
 	   (org-ascii--list-listings keyword info))))
        keyword info)))))
        keyword info)))))
 
 

+ 27 - 24
lisp/ox-html.el

@@ -2026,31 +2026,34 @@ a plist used as a communication channel."
 
 
 ;;; Tables of Contents
 ;;; Tables of Contents
 
 
-(defun org-html-toc (depth info)
+(defun org-html-toc (depth info &optional scope)
   "Build a table of contents.
   "Build a table of contents.
-DEPTH is an integer specifying the depth of the table.  INFO is a
-plist used as a communication channel.  Return the table of
-contents as a string, or nil if it is empty."
+DEPTH is an integer specifying the depth of the table.  INFO is
+a plist used as a communication channel.  Optional argument SCOPE
+is an element defining the scope of the table.  Return the table
+of contents as a string, or nil if it is empty."
   (let ((toc-entries
   (let ((toc-entries
 	 (mapcar (lambda (headline)
 	 (mapcar (lambda (headline)
 		   (cons (org-html--format-toc-headline headline info)
 		   (cons (org-html--format-toc-headline headline info)
 			 (org-export-get-relative-level headline info)))
 			 (org-export-get-relative-level headline info)))
-		 (org-export-collect-headlines info depth)))
-	(outer-tag (if (and (org-html-html5-p info)
-			    (plist-get info :html-html5-fancy))
-		       "nav"
-		     "div")))
+		 (org-export-collect-headlines info depth scope))))
     (when toc-entries
     (when toc-entries
-      (concat (format "<%s id=\"table-of-contents\">\n" outer-tag)
-	      (let ((top-level (plist-get info :html-toplevel-hlevel)))
-		(format "<h%d>%s</h%d>\n"
-			top-level
-			(org-html--translate "Table of Contents" info)
-			top-level))
-	      "<div id=\"text-table-of-contents\">"
-	      (org-html--toc-text toc-entries)
-	      "</div>\n"
-	      (format "</%s>\n" outer-tag)))))
+      (let ((toc (concat "<div id=\"text-table-of-contents\">"
+			 (org-html--toc-text toc-entries)
+			 "</div>\n")))
+	(if scope toc
+	  (let ((outer-tag (if (and (org-html-html5-p info)
+				    (plist-get info :html-html5-fancy))
+			       "nav"
+			     "div")))
+	    (concat (format "<%s id=\"table-of-contents\">\n" outer-tag)
+		    (let ((top-level (plist-get info :html-toplevel-hlevel)))
+		      (format "<h%d>%s</h%d>\n"
+			      top-level
+			      (org-html--translate "Table of Contents" info)
+			      top-level))
+		    toc
+		    (format "</%s>\n" outer-tag))))))))
 
 
 (defun org-html--toc-text (toc-entries)
 (defun org-html--toc-text (toc-entries)
   "Return innards of a table of contents, as a string.
   "Return innards of a table of contents, as a string.
@@ -2550,13 +2553,13 @@ CONTENTS is nil.  INFO is a plist holding contextual information."
     (cond
     (cond
      ((string= key "HTML") value)
      ((string= key "HTML") value)
      ((string= key "TOC")
      ((string= key "TOC")
-      (let ((value (downcase value)))
+      (let ((case-fold-search t))
 	(cond
 	(cond
 	 ((string-match "\\<headlines\\>" value)
 	 ((string-match "\\<headlines\\>" value)
-	  (let ((depth (or (and (string-match "[0-9]+" value)
-				(string-to-number (match-string 0 value)))
-			   (plist-get info :with-toc))))
-	    (org-html-toc depth info)))
+	  (let ((depth (and (string-match "\\<[0-9]+\\>" value)
+			    (string-to-number (match-string 0 value))))
+		(localp (org-string-match-p "\\<local\\>" value)))
+	    (org-html-toc depth info (and localp keyword))))
 	 ((string= "listings" value) (org-html-list-of-listings info))
 	 ((string= "listings" value) (org-html-list-of-listings info))
 	 ((string= "tables" value) (org-html-list-of-tables info))))))))
 	 ((string= "tables" value) (org-html-list-of-tables info))))))))
 
 

+ 41 - 14
lisp/ox-latex.el

@@ -953,11 +953,13 @@ file name as its single argument."
 
 
 (defcustom org-latex-logfiles-extensions
 (defcustom org-latex-logfiles-extensions
   '("aux" "bcf" "blg" "fdb_latexmk" "fls" "figlist" "idx" "log" "nav" "out"
   '("aux" "bcf" "blg" "fdb_latexmk" "fls" "figlist" "idx" "log" "nav" "out"
-    "run.xml" "snm" "toc" "vrb" "xdv")
+    "ptc" "run.xml" "snm" "toc" "vrb" "xdv")
   "The list of file extensions to consider as LaTeX logfiles.
   "The list of file extensions to consider as LaTeX logfiles.
-The logfiles will be remove if `org-latex-remove-logfiles' is
+The logfiles will be removed if `org-latex-remove-logfiles' is
 non-nil."
 non-nil."
   :group 'org-export-latex
   :group 'org-export-latex
+  :version "25.1"
+  :package-version '(Org . "8.3")
   :type '(repeat (string :tag "Extension")))
   :type '(repeat (string :tag "Extension")))
 
 
 (defcustom org-latex-remove-logfiles t
 (defcustom org-latex-remove-logfiles t
@@ -1536,7 +1538,23 @@ holding contextual information."
 			 (org-export-get-alt-title headline info)
 			 (org-export-get-alt-title headline info)
 			 section-back-end info)
 			 section-back-end info)
 			(and (eq (plist-get info :with-tags) t) tags)
 			(and (eq (plist-get info :with-tags) t) tags)
-			info)))
+			info))
+	      ;; Maybe end local TOC (see `org-latex-keyword').
+	      (contents
+	       (concat
+		contents
+		(let ((case-fold-search t)
+		      (section
+		       (let ((first (car (org-element-contents headline))))
+			 (and (eq (org-element-type first) 'section) first))))
+		  (org-element-map section 'keyword
+		    (lambda (k)
+		      (and (equal (org-element-property :key k) "TOC")
+			   (let ((v (org-element-property :value k)))
+			     (and (org-string-match-p "\\<headlines\\>" v)
+				  (org-string-match-p "\\<local\\>" v)
+				  (format "\\stopcontents[level-%d]" level)))))
+		    info t)))))
 	  (if (and numberedp opt-title
 	  (if (and numberedp opt-title
 		   (not (equal opt-title full-text))
 		   (not (equal opt-title full-text))
 		   (string-match "\\`\\\\\\(.*?[^*]\\){" section-fmt))
 		   (string-match "\\`\\\\\\(.*?[^*]\\){" section-fmt))
@@ -1754,18 +1772,27 @@ CONTENTS is nil.  INFO is a plist holding contextual information."
      ((string= key "LATEX") value)
      ((string= key "LATEX") value)
      ((string= key "INDEX") (format "\\index{%s}" value))
      ((string= key "INDEX") (format "\\index{%s}" value))
      ((string= key "TOC")
      ((string= key "TOC")
-      (let ((value (downcase value)))
+      (let ((case-fold-search t))
 	(cond
 	(cond
-	 ((string-match "\\<headlines\\>" value)
-	  (let ((depth (or (and (string-match "[0-9]+" value)
-				(string-to-number (match-string 0 value)))
-			   (plist-get info :with-toc))))
-	    (concat
-	     (when (wholenump depth)
-	       (format "\\setcounter{tocdepth}{%s}\n" depth))
-	     "\\tableofcontents")))
-	 ((string= "tables" value) "\\listoftables")
-	 ((string= "listings" value)
+	 ((org-string-match-p "\\<headlines\\>" value)
+	  (let* ((localp (org-string-match-p "\\<local\\>" value))
+		 (parent (org-element-lineage keyword '(headline)))
+		 (level (if (not (and localp parent)) 0
+			  (org-export-get-relative-level parent info)))
+		 (depth
+		  (and (string-match "\\<[0-9]+\\>" value)
+		       (format
+			"\\setcounter{tocdepth}{%d}"
+			(+ (string-to-number (match-string 0 value)) level)))))
+	    (if (and localp parent)
+		;; Start local TOC, assuming package "titletoc" is
+		;; required.
+		(format "\\startcontents[level-%d]
+\\printcontents[level-%d]{}{0}{%s}"
+			level level (or depth ""))
+	      (concat depth (and depth "\n") "\\tableofcontents"))))
+	 ((org-string-match-p "\\<tables\\>" value) "\\listoftables")
+	 ((org-string-match-p "\\<listings\\>" value)
 	  (case (plist-get info :latex-listings)
 	  (case (plist-get info :latex-listings)
 	    ((nil) "\\listoffigures")
 	    ((nil) "\\listoffigures")
 	    (minted "\\listoflistings")
 	    (minted "\\listoflistings")

+ 49 - 44
lisp/ox-odt.el

@@ -1080,13 +1080,20 @@ See `org-odt--build-date-styles' for implementation details."
 
 
 ;;;; Table of Contents
 ;;;; Table of Contents
 
 
-(defun org-odt-begin-toc (index-title depth)
+(defun org-odt--format-toc (title entries depth)
+  "Return a table of contents.
+TITLE is the title of the table, as a string, or nil.  ENTRIES is
+the contents of the table, as a string.  DEPTH is an integer
+specifying the depth of the table."
   (concat
   (concat
-   (format "
-    <text:table-of-content text:style-name=\"OrgIndexSection\" text:protected=\"true\" text:name=\"Table of Contents\">
-     <text:table-of-content-source text:outline-level=\"%d\">
-      <text:index-title-template text:style-name=\"Contents_20_Heading\">%s</text:index-title-template>
-" depth index-title)
+   "
+<text:table-of-content text:style-name=\"OrgIndexSection\" text:protected=\"true\" text:name=\"Table of Contents\">\n"
+   (format "  <text:table-of-content-source text:outline-level=\"%d\">" depth)
+   (and title
+	(format "
+    <text:index-title-template text:style-name=\"Contents_20_Heading\">%s</text:index-title-template>
+"
+		title))
 
 
    (let ((levels (number-sequence 1 10)))
    (let ((levels (number-sequence 1 10)))
      (mapconcat
      (mapconcat
@@ -1098,23 +1105,21 @@ See `org-odt--build-date-styles' for implementation details."
        <text:index-entry-chapter/>
        <text:index-entry-chapter/>
        <text:index-entry-text/>
        <text:index-entry-text/>
        <text:index-entry-link-end/>
        <text:index-entry-link-end/>
-      </text:table-of-content-entry-template>
-" level level)) levels ""))
-
-   (format  "
-     </text:table-of-content-source>
-
-     <text:index-body>
-      <text:index-title text:style-name=\"Sect1\" text:name=\"Table of Contents1_Head\">
-       <text:p text:style-name=\"Contents_20_Heading\">%s</text:p>
-      </text:index-title>
- " index-title)))
-
-(defun org-odt-end-toc ()
-  (format "
-     </text:index-body>
-    </text:table-of-content>
-"))
+      </text:table-of-content-entry-template>\n"
+	 level level)) levels ""))
+   "
+  </text:table-of-content-source>
+  <text:index-body>"
+   (and title
+	(format "
+    <text:index-title text:style-name=\"Sect1\" text:name=\"Table of Contents1_Head\">
+      <text:p text:style-name=\"Contents_20_Heading\">%s</text:p>
+    </text:index-title>\n"
+		title))
+   entries
+   "
+  </text:index-body>
+</text:table-of-content>"))
 
 
 (defun* org-odt-format-toc-headline
 (defun* org-odt-format-toc-headline
     (todo todo-type priority text tags
     (todo todo-type priority text tags
@@ -1149,7 +1154,12 @@ See `org-odt--build-date-styles' for implementation details."
   (format "<text:a xlink:type=\"simple\" xlink:href=\"#%s\">%s</text:a>"
   (format "<text:a xlink:type=\"simple\" xlink:href=\"#%s\">%s</text:a>"
 	  headline-label text))
 	  headline-label text))
 
 
-(defun org-odt-toc (depth info)
+(defun org-odt-toc (depth info &optional scope)
+  "Build a table of contents.
+DEPTH is an integer specifying the depth of the table.  INFO is
+a plist containing current export properties.  Optional argument
+SCOPE, when non-nil, defines the scope of the table.  Return the
+table of contents as a string, or nil."
   (assert (wholenump depth))
   (assert (wholenump depth))
   ;; When a headline is marked as a radio target, as in the example below:
   ;; When a headline is marked as a radio target, as in the example below:
   ;;
   ;;
@@ -1161,24 +1171,17 @@ See `org-odt--build-date-styles' for implementation details."
   ;; /TOC/, as otherwise there will be duplicated anchors one in TOC
   ;; /TOC/, as otherwise there will be duplicated anchors one in TOC
   ;; and one in the document body.
   ;; and one in the document body.
   ;;
   ;;
-  ;; FIXME-1: Currently exported headings are memoized.  `org-export.el'
-  ;; doesn't provide a way to disable memoization.  So this doesn't
-  ;; work.
-  ;;
-  ;; FIXME-2: Are there any other objects that need to be suppressed
+  ;; FIXME: Are there any other objects that need to be suppressed
   ;; within TOC?
   ;; within TOC?
-  (let* ((title (org-export-translate "Table of Contents" :utf-8 info))
-	 (headlines (org-export-collect-headlines
-		     info (and (wholenump depth) depth)))
+  (let* ((headlines (org-export-collect-headlines info depth scope))
 	 (backend (org-export-create-backend
 	 (backend (org-export-create-backend
-		   :parent (org-export-backend-name
-			    (plist-get info :back-end))
+		   :parent (org-export-backend-name (plist-get info :back-end))
 		   :transcoders (mapcar
 		   :transcoders (mapcar
 				 (lambda (type) (cons type (lambda (d c i) c)))
 				 (lambda (type) (cons type (lambda (d c i) c)))
 				 (list 'radio-target)))))
 				 (list 'radio-target)))))
     (when headlines
     (when headlines
-      (concat
-       (org-odt-begin-toc title depth)
+      (org-odt--format-toc
+       (and (not scope) (org-export-translate "Table of Contents" :utf-8 info))
        (mapconcat
        (mapconcat
 	(lambda (headline)
 	(lambda (headline)
 	  (let* ((entry (org-odt-format-headline--wrap
 	  (let* ((entry (org-odt-format-headline--wrap
@@ -1188,7 +1191,7 @@ See `org-odt--build-date-styles' for implementation details."
 	    (format "\n<text:p text:style-name=\"%s\">%s</text:p>"
 	    (format "\n<text:p text:style-name=\"%s\">%s</text:p>"
 		    style entry)))
 		    style entry)))
 	headlines "\n")
 	headlines "\n")
-       (org-odt-end-toc)))))
+       depth))))
 
 
 
 
 ;;;; Document styles
 ;;;; Document styles
@@ -2013,7 +2016,8 @@ contextual information."
 
 
 (defun org-odt-keyword (keyword contents info)
 (defun org-odt-keyword (keyword contents info)
   "Transcode a KEYWORD element from Org to ODT.
   "Transcode a KEYWORD element from Org to ODT.
-CONTENTS is nil.  INFO is a plist holding contextual information."
+CONTENTS is nil.  INFO is a plist holding contextual
+information."
   (let ((key (org-element-property :key keyword))
   (let ((key (org-element-property :key keyword))
 	(value (org-element-property :value keyword)))
 	(value (org-element-property :value keyword)))
     (cond
     (cond
@@ -2022,14 +2026,15 @@ CONTENTS is nil.  INFO is a plist holding contextual information."
       ;; FIXME
       ;; FIXME
       (ignore))
       (ignore))
      ((string= key "TOC")
      ((string= key "TOC")
-      (let ((value (downcase value)))
+      (let ((case-fold-search t))
 	(cond
 	(cond
-	 ((string-match "\\<headlines\\>" value)
-	  (let ((depth (or (and (string-match "[0-9]+" value)
+	 ((org-string-match-p "\\<headlines\\>" value)
+	  (let ((depth (or (and (string-match "\\<[0-9]+\\>" value)
 				(string-to-number (match-string 0 value)))
 				(string-to-number (match-string 0 value)))
-			   (plist-get info :with-toc))))
-	    (when (wholenump depth) (org-odt-toc depth info))))
-	 ((member value '("tables" "figures" "listings"))
+			   (plist-get info :headline-levels)))
+		(localp (org-string-match-p "\\<local\\>" value)))
+	    (org-odt-toc depth info (and localp keyword))))
+	 ((org-string-match-p "tables\\|figures\\|listings" value)
 	  ;; FIXME
 	  ;; FIXME
 	  (ignore))))))))
 	  (ignore))))))))
 
 

+ 22 - 9
lisp/ox.el

@@ -4828,7 +4828,7 @@ return nil."
 ;; `org-export-collect-tables', `org-export-collect-figures' and
 ;; `org-export-collect-tables', `org-export-collect-figures' and
 ;; `org-export-collect-listings' can be derived from it.
 ;; `org-export-collect-listings' can be derived from it.
 
 
-(defun org-export-collect-headlines (info &optional n)
+(defun org-export-collect-headlines (info &optional n scope)
   "Collect headlines in order to build a table of contents.
   "Collect headlines in order to build a table of contents.
 
 
 INFO is a plist used as a communication channel.
 INFO is a plist used as a communication channel.
@@ -4838,15 +4838,28 @@ the table of contents.  Otherwise, it is set to the value of the
 last headline level.  See `org-export-headline-levels' for more
 last headline level.  See `org-export-headline-levels' for more
 information.
 information.
 
 
+Optional argument SCOPE, when non-nil, is an element.  If it is
+a headline, only children of SCOPE are collected.  Otherwise,
+collect children of the headline containing provided element.  If
+there is no such headline, collect all headlines.  In any case,
+argument N becomes relative to the level of that headline.
+
 Return a list of all exportable headlines as parsed elements.
 Return a list of all exportable headlines as parsed elements.
-Footnote sections, if any, will be ignored."
-  (let ((limit (plist-get info :headline-levels)))
-    (setq n (if (wholenump n) (min n limit) limit))
-    (org-element-map (plist-get info :parse-tree) 'headline
-      #'(lambda (headline)
-	  (unless (org-element-property :footnote-section-p headline)
-	    (let ((level (org-export-get-relative-level headline info)))
-	      (and (<= level n) headline))))
+Footnote sections are ignored."
+  (let* ((scope (cond ((not scope) (plist-get info :parse-tree))
+		      ((eq (org-element-type scope) 'headline) scope)
+		      ((org-export-get-parent-headline scope))
+		      (t (plist-get info :parse-tree))))
+	 (limit (plist-get info :headline-levels))
+	 (n (if (not (wholenump n)) limit
+	      (min (if (eq (org-element-type scope) 'org-data) n
+		     (+ (org-export-get-relative-level scope info) n))
+		   limit))))
+    (org-element-map (org-element-contents scope) 'headline
+      (lambda (headline)
+	(unless (org-element-property :footnote-section-p headline)
+	  (let ((level (org-export-get-relative-level headline info)))
+	    (and (<= level n) headline))))
       info)))
       info)))
 
 
 (defun org-export-collect-elements (type info &optional predicate)
 (defun org-export-collect-elements (type info &optional predicate)

+ 13 - 1
testing/lisp/test-ox.el

@@ -3185,7 +3185,19 @@ Another text. (ref:text)
    (= 1
    (= 1
       (length
       (length
        (org-test-with-parsed-data "#+OPTIONS: H:1\n* H1\n** H2"
        (org-test-with-parsed-data "#+OPTIONS: H:1\n* H1\n** H2"
-	 (org-export-collect-headlines info 2))))))
+	 (org-export-collect-headlines info 2)))))
+  ;; Collect headlines locally.
+  (should
+   (= 2
+      (org-test-with-parsed-data "* H1\n** H2\n** H3"
+	(let ((scope (org-element-map tree 'headline #'identity info t)))
+	  (length (org-export-collect-headlines info nil scope))))))
+  ;; When collecting locally, optional level is relative.
+  (should
+   (= 1
+      (org-test-with-parsed-data "* H1\n** H2\n*** H3"
+	(let ((scope (org-element-map tree 'headline #'identity info t)))
+	  (length (org-export-collect-headlines info 1 scope)))))))