branch: externals/org
commit 35d12cddc700cac23137e65a67ab1ddc94bc0f46
Author: Pedro A. Aranda <paag...@gmail.com>
Commit: Ihor Radchenko <yanta...@posteo.net>

    ox-latex: Improve handling of unnumbered sections in TOC
    
    * lisp/ox-latex.el (org-latex-toc-include-unnumbered): New option
    controlling whether unnumbered headings are included into toc.
    (org-latex-headline): Refactor TOC title handling.  Fix exporting
    when there are unnumbered sections with ALT_TITLE.
    * doc/org-manual.org (Controlling the way the table of contents is 
generated):
    New section describing how TOC is handled in LaTeX export.
    * etc/ORG-NEWS (ox-latex: Table of contents generation has been fixed and 
augmented):
    Announce the changes.
    * testing/lisp/test-ox-latex.el (test-ox-latex/num-t):
    New test.
    
    Link: 
https://list.orgmode.org/orgmode/eab53afb-dd15-4436-a611-8a7f2fccf...@gmail.com/
    Link: 
https://list.orgmode.org/878qpw32da.fsf@localhost/T/#m4f95b816a92d1af72a1c7b2ac8e69764e14b04a2
---
 doc/org-manual.org            |  42 ++++++++++++++-
 etc/ORG-NEWS                  |  15 ++++++
 lisp/ox-latex.el              | 114 +++++++++++++++++++++++++++++++++-------
 testing/lisp/test-ox-latex.el | 118 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 267 insertions(+), 22 deletions(-)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index 65a1185d17..fa1bf5c7f6 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -14852,6 +14852,42 @@ some text in German...
 \end{foreigndisplayquote}
 #+end_example
 
+*** Controlling the way the table of contents is generated
+#+cindex: LaTeX ToC export
+
+When exporting your document to LaTeX, only numbered sections will be
+included. This is closer to LaTeX and different to how other exporters
+work (see [[*Table of Contents]]). If you need an unnumbered section
+to appear in the table of contents, use the property =UNNUMBERED= and
+set it to =toc=:
+
+#+begin_example
+:PROPERTIES:
+:UNNUMBERED: toc
+:END:
+#+end_example
+
+#+cindex: LaTeX ToC export a la ~org~
+#+cindex: @samp{org-latex-toc-include-unnumbered}
+If you want the LaTeX exporter to behave like other exporters,
+customise the variable ~org-latex-toc-include-unnumbered~ and
+set it to ~t~:
+
+#+begin_example
+(setq org-latex-toc-include-unnumbered t)
+#+end_example
+
+or add this setting in the local variables.
+
+In this case, unnumbered sections will be included in the table of
+contents, unless you set the =UNNUMBERED= property to =notoc=:
+
+#+begin_example
+:PROPERTIES:
+:UNNUMBERED: notoc
+:END:
+#+end_example
+
 ** Markdown Export
 :PROPERTIES:
 :DESCRIPTION: Exporting to Markdown.
@@ -23509,8 +23545,10 @@ Beamer---, the =org-latex-package-alist= variable 
needs further
 configuration.  See [[LaTeX specific export settings]].
 
 [fn:43] At the moment, some export backends do not obey this
-specification.  For example, LaTeX export excludes every unnumbered
-headline from the table of contents.
+specification.  For example, LaTeX export excludes by default
+every unnumbered headline from the table of contents, unless you set
+the custom variable =org-latex-toc-include-unnumbered= to =t= or add
+=:UNNUMBERED: toc= to the section's properties.
 
 [fn:44] Note that ~org-link-search-must-match-exact-headline~ is
 locally bound to non-~nil~.  Therefore, ~org-link-search~ only matches
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 9eb4f711c1..7f067481e2 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -305,6 +305,21 @@ slide to specific animation steps.
 This text will be displayed on animation step 2 and later.
 #+END_SRC
 
+*** ox-latex: Table of contents generation has been fixed and augmented
+
+The LaTeX exporter differs from other exporters in that it *does not*
+export unnumbered sections by default.
+
+The LaTeX exporter will use the new property value =:UNNUMBERED: toc= to
+include unnumbered sections in the table of contents.
+
+Additionally, a new custom variable
+~org-latex-toc-include-unnumbered~ has been introduced. It
+enables including unnumbered sections in the ToC aligned with the
+behaviour of other exporters. In this case, to exclude a section from
+the table of contents, mark it as =:UNNUMBERED: notoc= in its
+properties.
+
 ** New functions and changes in function arguments
 
 # This also includes changes in function behavior from Elisp perspective.
diff --git a/lisp/ox-latex.el b/lisp/ox-latex.el
index bc31df6f52..8ec8c25dae 100644
--- a/lisp/ox-latex.el
+++ b/lisp/ox-latex.el
@@ -643,7 +643,8 @@ precedence over this variable."
   :version "26.1"
   :package-version '(Org . "8.3")
   :type '(choice (const :tag "No template" nil)
-                (string :tag "Format string")))
+                (string :tag "Format string"))
+  :safe #'string-or-null-p)
 
 ;;;; Headline
 
@@ -675,6 +676,7 @@ command like \"\\sidenote{%s%s}\" that you want to use.
 The value will be passed as an argument to `format' as the following
   (format org-latex-default-footnote-command
      footnote-description footnote-label)"
+
   :group 'org-export-latex
   :package-version '(Org . "9.7")
   :type 'string)
@@ -1537,6 +1539,18 @@ calling `org-latex-compile'."
           (string :tag "Message"))))
 
 
+(defcustom org-latex-toc-include-unnumbered nil
+  "Whether to include unnumbered headings in the table of contents.
+
+The default behaviour is to include numbered headings only, as it is
+usually the case in LaTeX (but different from other Org exporters).
+To include an unnumbered heading, set the `:UNNUMBERED:'
+property to `toc'"
+  :group 'org-export-latex
+  :package-version '(Org . "9.8")
+  :type 'boolean
+  :safe #'booleanp)
+
 
 ;;; Internal Functions
 
@@ -2275,6 +2289,12 @@ holding contextual information."
   (unless (org-element-property :footnote-section-p headline)
     (let* ((class (plist-get info :latex-class))
           (level (org-export-get-relative-level headline info))
+           ;; "LaTeX TOC handling"
+           ;; :unnumbered: toc will add the heading to the ToC
+           ;; "Org TOC handling"
+           ;; :unnumbered: notoc to suppress heading from the ToC
+           ;; else include all headings (including unnumbered) like other modes
+           (unnumbered-type (org-export-get-node-property :UNNUMBERED headline 
t))
           (numberedp (org-export-numbered-headline-p headline info))
           (class-sectioning (assoc class (plist-get info :latex-classes)))
           ;; Section formatting will set two placeholders: one for
@@ -2381,6 +2401,8 @@ holding contextual information."
               (funcall (plist-get info :latex-format-headline-function)
                        todo todo-type priority
                        (org-export-data-with-backend
+                         ;; Returns alternative title when provided or
+                         ;; title itself.
                         (org-export-get-alt-title headline info)
                         section-backend info)
                        (and (eq (plist-get info :with-tags) t) tags)
@@ -2401,25 +2423,77 @@ holding contextual information."
                                  (string-match-p "\\<local\\>" v)
                                  (format "\\stopcontents[level-%d]" level)))))
                    info t)))))
-         (if (and (or (and opt-title (not (equal opt-title full-text)))
-                       ;; Heading contains footnotes.  Add optional title
-                       ;; version without footnotes to avoid footnotes in
-                       ;; TOC/footers.
-                       (and (not (equal full-text-no-footnote full-text))
-                            (setq opt-title full-text-no-footnote)))
-                  (string-match "\\`\\\\\\(.+?\\){" section-fmt))
-              (format (replace-match "\\1[%s]" nil nil section-fmt 1)
-                     ;; Replace square brackets with parenthesis
-                     ;; since square brackets are not supported in
-                     ;; optional arguments.
-                     (replace-regexp-in-string
-                      "\\[" "(" (replace-regexp-in-string "\\]" ")" opt-title))
-                     full-text
-                     (concat headline-label pre-blanks contents))
-           ;; Impossible to add an alternative heading.  Fallback to
-           ;; regular sectioning format string.
-           (format section-fmt full-text
-                   (concat headline-label pre-blanks contents))))))))
+          ;; When do we need to explicitly specify a heading for TOC?
+          ;; 1. On numbered section with footnotes in title or alt_title
+          ;; 2. On an unnumbered section if :UNNUMBERED: allows it regardless 
of footnotes
+          ;; This applies to anything that may go into the ToC.
+          ;; Specifically for paragraphs, see first answer of
+          ;; 
https://tex.stackexchange.com/questions/288072/footnotes-within-paragraph
+          (let ((section-kw
+                 (and (string-match "\\`\\\\\\(.+?\\){" section-fmt)
+                      (match-string 1 section-fmt)))
+                need-alternative-toc-title)
+            (if (not section-kw)
+                ;; We only know how to add \SECTION-KW{...} to TOC.
+                (setq need-alternative-toc-title nil)
+              (if (string-suffix-p "*" section-kw)
+                  ;; FIXME: In theory, user may customize section-fmt
+                  ;; to use, e.g. \section{...} for unnumbered headings
+                  ;; We do not handle such scenario.
+                  (progn ;; unnumbered sections (ending with *)
+                    ;; Then we need to obey what the :UNNUMBERED: property says
+                    (if org-latex-toc-include-unnumbered
+                        ;; Treat the ToC closer to what other exporters do
+                        ;; Include unnumbered section into TOC unless
+                        ;; explicitly requested not to.
+                        (if (string= unnumbered-type "notoc")
+                            (setq need-alternative-toc-title nil)
+                          (setq need-alternative-toc-title t))
+                      ;; Ignore unnumbered headings in ToC - as in LaTeX
+                      ;; unless explicitly requested to include.
+                      (if (string= unnumbered-type "toc")
+                          (setq need-alternative-toc-title t)
+                        (setq need-alternative-toc-title nil))))
+                ;; Numbered sections
+                ;; Specify special TOC title only when there is
+                ;; opt-title or when title contains footnotes.
+                (if (and (string= full-text full-text-no-footnote)  ;; no 
footnotes
+                         ;; opt-title is either ALT_TITLE or title itself
+                         ;; as returned by `org-export-get-alt-title'
+                         (string= full-text opt-title)) ;; same alternative 
title
+                    (setq need-alternative-toc-title nil)
+                  (setq need-alternative-toc-title t))))
+            ;; In all cases
+            ;; Get rid of the footnotes in opt-title
+            (when (and (not (string= full-text-no-footnote full-text)) ;; when 
we have footnotess
+                       (string= full-text opt-title))      ;; And we do not 
impose an alternative title
+              (setq opt-title full-text-no-footnote))
+           (if need-alternative-toc-title
+                (let ((new-format section-fmt)
+                      (new-extra  "")) ;; put the addcontentsline here
+                  (if (string-suffix-p "*" section-kw)
+                      ;; Subsection that needs alternative title:
+                      ;; Keep section format, use \\addcontentsline
+                      (setq new-extra
+                            (format "\\addcontentsline{toc}{%s}{%s}\n"
+                                    (string-remove-suffix "*" section-kw)
+                                    opt-title))
+                    ;; section... we need the brackets
+                    (let*
+                       ;; Replace square brackets with parenthesis
+                       ;; since square brackets are not supported in
+                       ;; optional arguments.
+                        ((un-bracketed-alt (replace-regexp-in-string
+                                            "\\[" "(" 
(replace-regexp-in-string "\\]" ")" opt-title)))
+                         (replacement-re (concat "\\1[" un-bracketed-alt "]")))
+                      (setq new-format (replace-match replacement-re nil nil 
section-fmt 1))))
+                  (format new-format
+                          full-text
+                         (concat headline-label new-extra pre-blanks 
contents)))
+             ;; Don't need or cannot have alternative heading.
+             ;; Use regular sectioning format string.
+             (format section-fmt full-text
+                     (concat headline-label pre-blanks contents)))))))))
 
 (defun org-latex-format-headline-default-function
     (todo _todo-type priority text tags _info)
diff --git a/testing/lisp/test-ox-latex.el b/testing/lisp/test-ox-latex.el
index b75921ae78..04164767c0 100644
--- a/testing/lisp/test-ox-latex.el
+++ b/testing/lisp/test-ox-latex.el
@@ -154,5 +154,123 @@ Column & Column \\\\
      (search-forward
       
"\\href{https://orgmode.org/worg/images/orgmode/org-mode-unicorn.svg}{\\includegraphics[width=.9\\linewidth]{/wallpaper.png}}";))))
 
+(ert-deftest test-ox-latex/num-t ()
+  "Test toc treatment for fixed num:t"
+  (org-test-with-exported-text
+   'latex
+   "#+TITLE: num: fix
+#+OPTIONS: toc:t H:3 num:t
+
+* Section
+
+** Subsection 1
+:PROPERTIES:
+:UNNUMBERED: t
+:END:
+is suppressed
+** Subsection 2
+:PROPERTIES:
+:UNNUMBERED: toc
+:END:
+
+** Subsection 3
+:PROPERTIES:
+:UNNUMBERED: toc
+:ALT_TITLE: Alternative
+:END:
+
+* Section 2[fn::Test]
+:PROPERTIES:
+:ALT_TITLE: SECTION 2
+:END:
+"
+   (goto-char (point-min))
+   (should
+    (search-forward "\\begin{document}
+
+\\maketitle
+\\tableofcontents
+
+\\section{Section}
+\\label{"))
+   (should (search-forward "}
+
+\\subsection*{Subsection 1}
+\\label{"))
+   (should (search-forward "}
+is suppressed
+\\subsection*{Subsection 2}
+\\label{"))
+  (should (search-forward "}
+\\addcontentsline{toc}{subsection}{Subsection 2}
+\\subsection*{Subsection 3}
+\\label{"))
+  (should (search-forward "}
+\\addcontentsline{toc}{subsection}{Alternative}
+\\section[SECTION 2]{Section 2\\footnote{Test}}
+\\label{"))
+  (should (search-forward "}
+\\end{document}"))))
+
+(ert-deftest test-ox-latex/new-toc-as-org ()
+  "test toc treatment with `org-latex-toc-include-unnumbered' set to `t'"
+  (let ((org-latex-toc-include-unnumbered t))
+    (org-test-with-exported-text 'latex
+        "#+TITLE: num: fix
+#+OPTIONS: toc:t H:3 num:nil
+
+* Section
+
+** Subsection 1
+
+** Subsection 2
+:PROPERTIES:
+:UNNUMBERED: notoc
+:END:
+is suppressed
+
+** Subsection 3
+:PROPERTIES:
+:ALT_TITLE: Alternative
+:END:
+
+* Section 2[fn::Test]
+:PROPERTIES:
+:ALT_TITLE: SECTION 2
+:END:
+
+* Section 3[fn::Test]
+"
+      (goto-char (point-min))
+      (should (search-forward "\\begin{document}
+
+\\maketitle
+\\tableofcontents
+
+\\section*{Section}
+\\label{"))
+      (should (search-forward "}
+\\addcontentsline{toc}{section}{Section}
+
+\\subsection*{Subsection 1}
+\\label{"))
+      (should (search-forward "}
+\\addcontentsline{toc}{subsection}{Subsection 1}
+
+\\subsection*{Subsection 2}
+\\label{"))
+      (should (search-forward "}
+is suppressed
+\\subsection*{Subsection 3}
+\\label{"))
+      (should (search-forward "}
+\\addcontentsline{toc}{subsection}{Alternative}
+\\section*{Section 2\\footnote{Test}}
+\\label{"))
+      (should (search-forward "}
+\\addcontentsline{toc}{section}{SECTION 2}"))
+      (should (search-forward "}
+\\addcontentsline{toc}{section}{Section 3}")))))
+
 (provide 'test-ox-latex)
 ;;; test-ox-latex.el ends here

Reply via email to