Pedro Andres Aranda Gutierrez <paag...@gmail.com> writes: >> May you please explain this change? > > At one point, while debugging, it helped me assuring need-alternative being a > boolean. Ignore if you don’t like it. >>> - ;; legacy behavior (á la LaTeX) >>> + ;; Treat the ToC as it has been originally >> >> Maybe "Ignore unnumbered headings in ToC - as in LaTeX" > > OK, could live with that.
I have updated your patch, addressing my comments, adding some refactoring, and fixing a bug I found. See the attached updated patch. Main changes: 1. (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)) You previously had (string= full-text-no-footenote opt-title), which makes no sense. I also added a test for the case addressed by this part of the code. 2. I renamed need-alternative to need-toc. IMHO, it is a bit more clear. I also updated the commentary to explain what is going on. 3. I added a FIXME ;; FIXME: In theory, user may customize section-fmt ;; to use, e.g. \section{...} for unnumbered headings ;; We do not handle such scenario. Not sure if it is something we need to worry about. In addition, I have a question about (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)))) I am wondering if we can sompletely drop the part with using \sectionkwd[...]{...} and instead always use \addcontentsline. This way, we won't need to replace square brackets in the title. WDYT?
>From 278f4dd91919c505e108f3d0b4714c363c6be55c Mon Sep 17 00:00:00 2001 Message-ID: <278f4dd91919c505e108f3d0b4714c363c6be55c.1740302355.git.yanta...@posteo.net> From: "Pedro A. Aranda" <paag...@gmail.com> Date: Wed, 12 Feb 2025 07:27:27 +0100 Subject: [PATCH v2] New combined patch for ToC handling Ihor's comments and fix alternative title handling --- doc/org-manual.org | 41 +++++++++++- etc/ORG-NEWS | 15 +++++ lisp/ox-latex.el | 114 ++++++++++++++++++++++++++------ testing/lisp/test-ox-latex.el | 118 ++++++++++++++++++++++++++++++++++ 4 files changed, 266 insertions(+), 22 deletions(-) diff --git a/doc/org-manual.org b/doc/org-manual.org index 65a1185d17..dcc9a6dec0 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -14852,6 +14852,41 @@ *** Quote blocks in LaTeX export \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. 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 +23544,10 @@ * Footnotes 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..dbfcdacf9c 100644 --- a/lisp/ox-latex.el +++ b/lisp/ox-latex.el @@ -643,7 +643,8 @@ (defcustom org-latex-hyperref-template :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 @@ (defcustom org-latex-default-footnote-command "\\footnote{%s%s}" 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 @@ (defcustom org-latex-known-warnings (string :tag "Message")))) +(defcustom org-latex-toc-include-unnumbered nil + "Set this variable to true to include unnumbered headings in the +table of contents as other exporters do. + +The default behaviour is to include numbered headings only. +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 @@ (defun org-latex-headline (headline contents info) (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 @@ (defun org-latex-headline (headline contents info) (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 @@ (defun org-latex-headline (headline contents info) (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-toc) + (if (not section-kw) + ;; We only know how to add \SECTION-KW{...} to TOC. + (setq need-toc 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-toc nil) + (setq need-toc t)) + ;; Ignore unnumbered headings in ToC - as in LaTeX + ;; unless explicitly requested to include. + (if (string= unnumbered-type "toc") + (setq need-toc t) + (setq need-toc 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-toc nil) + (setq need-toc 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-toc + (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 @@ (ert-deftest test-ox-latex/inline-image () (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 -- 2.47.1
-- Ihor Radchenko // yantar92, Org mode maintainer, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92>