1

I've looked through the manual but can't seem to find the answer. Is it possible to suppress the TOC when exporting to ascii, but have it export to html?

1 Answer 1

1

EDIT: Simpler answer

This answer is really along the same lines that the answer in the executive summary follows, but it is a bit more streamlined to make it easier for a new Emacs user to use. The Org mode file will not need the code blocks because we are not going to advise the function. Instead, we are going to change the global value of the org-export-options-alist variable in the init file. And since we are making changes to the init file, we are going to add the macro definition to it as well, so that it will always be available for every Org mode file.

That means that the only thing needed in the Org mode file to take advantage of this capability is the modified #+OPTIONS line:

#+OPTIONS: toc:{{{if-ascii-else(nil, t)}}}

* section
foo

* bar
bar

The modifications to the init file are: 1) to modify the global value of the org-export-options-alist value and 2) to add the definition of the if-ascii-else macro.

The modification of org-export-options-alist is bog-standard: we need to do it after ox.el[c] is loaded, since the variable is defined in that file, but that's what eval-after-load is for:

(eval-after-load 'ox
   (setq org-export-options-alist
       (cons '(:options "OPTIONS" nil nil parse) 
             org-export-options-alist)))

This modifies the global value of org-export-options-alist but the effect on export is the same as in the "executive summary" answer.

Adding the macro similarly requires some code to be executed after the appropriate file is loaded:

(eval-after-load 'ox
    (add-to-list 'org-export-global-macros
             '("if-ascii-else" . "(eval (if (org-export-derived-backend-p org-export-current-backend 'ascii) $1 $2))"))

org-export-global-macros is a list of macros available globally. See its doc string with C-h v org-export-global-macros.

After adding these two snippets to your init file, restart Emacs so they take effect and export normally: C-c C-e t A e.g

The advantage here is that you can copy these two snippets of code into your init file and run with it, without understanding what they do, but I hope you will study the rest of the answer and get at least a glimpse of how to do these things in the future.


[Original answer starts here.]

[This was harder than it should be, although there might be a better way to do it than what I provide below. I suggest you wait a few days to see if somebody comes up with a better answer.]

Executive Summary

Here's an Org mode file that will produce a TOC only if you are not exporting to ascii:

#+MACRO: if-ascii-else (eval (if (org-export-derived-backend-p org-export-current-backend 'ascii) $1 $2))

#+OPTIONS: toc:{{{if-ascii-else(nil, t)}}}

Options should be {{{keyword(OPTIONS)}}}

* section
foo

* bar
bar


* Code                                                  :noexport:

#+name: advice-add
#+begin_src elisp
  (defun my/org-export-dispatch (orig-func &optional arg)
     (let ((org-export-options-alist (cons '(:options "OPTIONS" nil nil parse) org-export-options-alist)))
       (funcall orig-func arg)))

  (advice-add 'org-export-dispatch :around #'my/org-export-dispatch)
#+end_src

#+name: advice-remove
#+begin_src elisp
 (advice-remove 'org-export-dispatch #'my/org-export-dispatch)
#+end_src

Before you press C-e to to start the export dispatcher, you will have to press C-c C-c on the first code block (the one named advice-add). You can check that the advice has been added with C-h f org-export-dispatch and checking the last line: it should say This function has :around advice: ‘my/org-export-dispatch’. Then, you should be able to export normally: C-c C-e t A e.g to export to an ascii buffer or C-c C-e h o to export to an HTML file and open it.

Afterwards, you can remove the advice by pressing C-c C-c on the second code block (the one name advice-remove) to put everything back to normal. You can also leave the advice in force, assuming it causes no problems - you can even add it to your init file, so that it will be available in the future, after you have tested it thoroughly. You will have to make sure that org-export-dispatch is defined already before you can add the advice to it, either by doing a (require 'ox) or by doing the advice-add within an eval-after-load. See this answer for some details on a similar problem.


All the gory details

The idea is to use a macro to set the toc option to either nil or t, depending on whether the export backend is (derived from) ascii or not. The following macro can do that:

#+MACRO: if-ascii-else (eval (if (org-export-derived-backend-p org-export-current-backend 'ascii) $1 $2))

#+OPTIONS: toc:{{{if-ascii-else(nil, t)}}}

Options should be {{{keyword(OPTIONS)}}}

This is a modified version of a similar macro that I cribbed from @fniessen's collection.

If you add the above to your file and try exporting, everything works, except that you still get a TOC even in ascii export. It turns out that macros are expanded in certain keywords, but not in the #+OPTIONS keyword. I added the last line for debugging, to make sure that the replacement does happen properly (it does) in normal context. It just does not happen where it would matter.

It turns out the the org-export-as function calls the macro processor as follows:

    ...
    (org-macro-replace-all org-macro-templates parsed-keywords)
    ...

where parsed-keywords is the list of keywords where macro expansion will happen (see the doc string of org-macro-replace-all with C-h f org-macro-replace-all).

So I modified that line to

    ...
    (org-macro-replace-all org-macro-templates (cons "OPTIONS" parsed-keywords))
    ...

and lo and behold! the exported output did not contain the TOC.

The trick now is to do the above without modifying the code explicitly. There are probably multiple ways to do that, but I figured that if I could change whatever variable was used to calculate parsed-keywords in org-export-dispatch, maybe I could let-bind the variable in an advice of the function.

The parsed-keywords variable is let-bound through the following code in org-export-as:

           ...
           (parsed-keywords
            (delq nil
              (mapcar (lambda (o) (and (eq (nth 4 o) 'parse) (nth 1 o)))
                  (append (org-export-get-all-options backend)
                      org-export-options-alist))))
           ...

which basically constructs a list of keyword names out of org-export-options-alist. Looking at the latter with C-h v org-export-options-alist shows that "OPTIONS" is indeed not included in it. org-export-options-alist is not a simple list of names however, so the modification is a bit more complicated:

    (let ((org-export-options-alist (cons '(:options "OPTIONS" nil nil parse) org-export-options-alist)))
        (org-export-dispatch))

We cons an entry onto the front of org-export-options-alist, let-bind the result to org-export-options-alist and call org-export-dispatch with that let-binding in place. And... it works: if you go to your Org mode buffer and use M-: with the above expression, instead of using C-e which just calls org-export-dispatch, you can use t A e.g. to export to a buffer and you get no TOC.

We can now use the advice mechanism to add the above modification to org-export-dispatch. I like adding a Code section to the Org file itself (with a noexport tag, so that it is ignored by the exporter) and add code blocks that do the modifications I want:

* Code                                                   :noexport

#+name: advice-add
#+begin_src elisp
  (defun my/org-export-dispatch (orig-func &optional arg)
     (let ((org-export-options-alist (cons '(:options "OPTIONS" nil nil parse) org-export-options-alist)))
       (funcall orig-func arg)))

  (advice-add 'org-export-dispatch :around #'my/org-export-dispatch)
#+end_src

Pressing C-c C-c on the code block executes it, thereby defining the my/org-export-dispatch function and then adding it as an around advice to org-export-dispatch. The my/org-export-dispatch function let-binds the org-export-options-alist just as we did manually earlier, before calling the original function that was advised, i.e. org-export-dispatch.

I hope the step-by-step helps but you might have to read it a couple of times. In the meantime, the executive summary should suffice.

2
  • thanks.. way more complicated than I hoped for, but really appreciate your response.
    – RoyM
    Commented Dec 25, 2022 at 14:50
  • 1
    PTAL at the "simpler answer" at the top. I think it will be easier to use.
    – NickD
    Commented Dec 26, 2022 at 2:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.