From a2819314514ddd4e2ccd48301827ed4c1c875d85 Mon Sep 17 00:00:00 2001 From: "Pedro A. Aranda Gutiérrez" Date: Tue, 7 Jan 2025 17:20:26 +0100 Subject: [PATCH] ox-latex: fix toc generation * lisp/ox-latex.el: Add variable `org-latex-unnumbered-section-re' to filter the unnumbered section. (org-latex-headline): fix 'num:t' toc generation to match other exporters and add a new 'num:latex' option that behaves closer to toc generation criteria in LaTeX * testing/lisp/test-ox-latex.el: test the fix and new toc generation option. * doc/org-manual.org: Document the fix and new toc generation option * etc/ORG-NEWS: Announce fix to toc generation and the new `num:latex' option --- doc/org-manual.org | 25 ++++++++ etc/ORG-NEWS | 13 ++++ lisp/ox-latex.el | 46 ++++++++++++-- testing/lisp/test-ox-latex.el | 111 ++++++++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+), 6 deletions(-) diff --git a/doc/org-manual.org b/doc/org-manual.org index e88317379..186ad1258 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -14852,6 +14852,31 @@ some text in German... \end{foreigndisplayquote} #+end_example +*** Controlling the way the table of contents is generated + +=org= handles the table of contents slightly different as LaTeX. It +will be generated when the =#+OPTIONS:= include =toc:t= and include +all headings, regardless of whether they are numbered (=num:t= )or +unnumbered (=num:nil=). + +If you want the table of contents to follow the LaTeX behaviour +closer, you will can set =num:latex= in the options line. In this +case, the following rules apply: + +1. Numbered headings are included in the header. If you want an + alternative title to appear in the table of contents, use the + =:ALT_TITLE:= property for the section. + +2. Unnumbered headings are not included in the table of contents, + unless you set the =:UNNUMBERED:= property to =toc= or you include + the =:ALT_TITLE:= property in the section's properies. + +If your LaTeX output uses special commands for unnumbered headings +instead of the expected =\chapter*{}= or =\section*{}=, +=\subsection*{}= etc. you need to configure +=org-latex-unnumbered-section-re= to add the headings to the +table of contents. + ** Markdown Export :PROPERTIES: :DESCRIPTION: Exporting to Markdown. diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index c9bb192de..c9cb3980e 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -291,6 +291,19 @@ slide to specific animation steps. This text will be displayed on animation step 2 and later. #+END_SRC +*** ox-latex: The way the table of contents handles unnumbered sections has changed + +The document option ~num~ can now be ~t~, ~nil~ or ~latex~. The LaTeX +exporter will now follow the convention of the other exporters and +export all unnumbered sections to the table of contents (ToC). + +If you want to follow your "LaTeX intuition" and control which unnumbered +sections appear in the ToC, set ~num:latex~ in the document +~#+OPTIONS:~. In this case, you will be able include the unnumbered +section in the table of contents by either adding an ~:ALT_TITLE:~ or +setting ~:UNNUMBERED:~ to ~toc~. Setting ~:UNNUMBERED:~ to ~t~ will +exclude the section from the ToC. + ** 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 16f8f5af2..3acc24e20 100644 --- a/lisp/ox-latex.el +++ b/lisp/ox-latex.el @@ -319,6 +319,10 @@ cdr is a property list. Valid keywords for this list can be: "empheq"))) "Regexp of LaTeX math environments.") +(defconst org-latex-unnumbered-section-re + "^\\\\\\(chapter\\|\\(sub\\)*section\\)[*]" + "Regexp to get the unnumbered section tag") + ;;; User Configurable Variables @@ -2276,6 +2280,8 @@ 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)) + (sec-num (plist-get info :section-numbers)) + (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 @@ -2378,7 +2384,8 @@ holding contextual information." ;; This is a standard headline. Export it as a section. Add ;; an alternative heading when possible, and when this is not ;; identical to the usual heading. - (let ((opt-title + (let (;; (alt-title (org-export-get-alt-title headline info)) + (opt-title (funcall (plist-get info :latex-format-headline-function) todo todo-type priority (org-export-data-with-backend @@ -2402,10 +2409,10 @@ holding contextual information." (string-match-p "\\" v) (format "\\stopcontents[level-%d]" level))))) info t))))) - (if (and (or (and opt-title (not (equal opt-title full-text))) + (if (and numberedp (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. + ;; TOC/footers for numbered sections *only* (and (not (equal full-text-no-footnote full-text)) (setq opt-title full-text-no-footnote))) (string-match "\\`\\\\\\(.+?\\){" section-fmt)) @@ -2418,9 +2425,36 @@ holding contextual information." 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)))))))) + ;; regular sectioning format string for a numbered section + (if numberedp + (format section-fmt full-text + (concat headline-label pre-blanks contents)) + ;; If the section is unnumbered, we can still do something + ;; + (save-match-data + (let ((candidate (string-match org-latex-unnumbered-section-re section-fmt))) + + (when (or + ;; Always suppress :UNNUMBERED: notoc + (string= unnumbered-type "notoc") + ;; When numbering mode is 'latex' + ;; suppress unnumbered 't' without alternative title + ;; :ALT_TITLE: + (and (string= sec-num "latex") + (string= unnumbered-type "t") + (string= opt-title full-text))) + (setq candidate nil)) + (format section-fmt full-text + (concat + ;; Match only the 'canonical' chapter, section,subsection, etc + (when candidate + ;; opt-title carries the section carries ALT_TITLE if defined + ;; or the subsection title if ALT_TITLE is not defined (!) + (format "\\addcontentsline{toc}{%s}{%s}\n" + (match-string 1 section-fmt) + opt-title)) + 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 892ac4437..fd3c2444d 100644 --- a/testing/lisp/test-ox-latex.el +++ b/testing/lisp/test-ox-latex.el @@ -127,5 +127,116 @@ 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-latex () + "Test toc treatment for num:latex" + (org-test-with-exported-text + 'latex + "#+TITLE: num-latex +#+OPTIONS: toc:t H:3 num:latex + +* Section + +** Subsection 1 +:PROPERTIES: +:UNNUMBERED: t +:END: + +** Subsection 2 +:PROPERTIES: +:UNNUMBERED: toc +:END: + +** Subsection 3 +:PROPERTIES: +:UNNUMBERED: toc +:ALT_TITLE: Alternative +:END: + +* Section 2 +: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 "} +\\subsection*{Subsection 2} +\\addcontentsline{toc}{subsection}{Subsection 2} +\\label{")) + (should (search-forward "} +\\subsection*{Subsection 3} +\\addcontentsline{toc}{subsection}{Alternative} +\\label{")) + (should (search-forward "} +\\section[SECTION 2]{Section 2} +\\label{")) + (should (search-forward "} +\\end{document}")))) + +(ert-deftest test-ox-latex/org-toc () + "Test table of contents generation à la org" + (org-test-with-exported-text + 'latex + "#+TITLE: num-latex +#+OPTIONS: toc:t H:3 num:t + +* Section + +** Subsection 1 +:PROPERTIES: +:UNNUMBERED: t +:END: + +** Subsection 2 +:PROPERTIES: +:UNNUMBERED: toc +:END: + +** Subsection 3 +:PROPERTIES: +:UNNUMBERED: toc +:ALT_TITLE: Alternative +:END: + +* Section 2 +:PROPERTIES: +:ALT_TITLE: SECTION 2 +:END: +" + (should + (search-forward "\\begin{document} + +\\maketitle +\\tableofcontents")) + (should + (search-forward "\\section{Section} +\\label{")) + (should ;; test :UNNUMBERED: t + (search-forward "\\subsection*{Subsection 1} +\\addcontentsline{toc}{subsection}{Subsection 1} +\\label{")) + (should ;; test :UNNUMBERED: toc + (search-forward "\\subsection*{Subsection 2} +\\addcontentsline{toc}{subsection}{Subsection 2} +\\label{")) + (should ;; test :UNNUMBERED: t with :ALT_TITLE: + (search-forward "\\subsection*{Subsection 3} +\\addcontentsline{toc}{subsection}{Alternative} +\\label{")) + (should ;; test :ALT_TITLE: only + (search-forward "\\section[SECTION 2]{Section 2} +\\label{")))) + (provide 'test-ox-latex) ;;; test-ox-latex.el ends here -- 2.34.1