13.17 Advanced Export Configuration

Export hooks

The export process executes two hooks before the actual exporting begins. The first hook, org-export-before-processing-hook, runs before any expansions of macros, Babel code, and include keywords in the buffer. The second hook, org-export-before-parsing-hook, runs before the buffer is parsed.

Functions added to these hooks are called with a single argument: the export back-end actually used, as a symbol. You may use them for heavy duty structural modifications of the document. For example, you can remove every headline in the buffer during export like this:

(defun my-headline-removal (backend)
  "Remove all headlines in the current buffer.
BACKEND is the export back-end being used, as a symbol."
  (org-map-entries
   (lambda ()
     (delete-region (point) (line-beginning-position 2))
     ;; We need to tell `org-map-entries' to not skip over heading at
     ;; point. Otherwise, it would continue from _next_ heading. See
     ;; the docstring of `org-map-entries' for details.
     (setq org-map-continue-from (point)))))

(add-hook 'org-export-before-parsing-hook #'my-headline-removal)

Filters

Filters are lists of functions to be applied to certain parts for a given back-end. The output from the first function in the filter is passed on to the next function in the filter. The final output is the output from the final function in the filter.

The Org export process has many filter sets applicable to different types of objects, plain text, parse trees, export options, and final output formats. The filters are named after the element type or object type: org-export-filter-TYPE-functions, where TYPE is the type targeted by the filter. Valid types are:

bodyboldbabel-call
center-blockclockcode
diary-sexpdrawerdynamic-block
entityexample-blockexport-block
export-snippetfinal-outputfixed-width
footnote-definitionfootnote-referenceheadline
horizontal-ruleinline-babel-callinline-src-block
inlinetaskitalicitem
keywordlatex-environmentlatex-fragment
line-breaklinknode-property
optionsparagraphparse-tree
plain-listplain-textplanning
property-drawerquote-blockradio-target
sectionspecial-blocksrc-block
statistics-cookiestrike-throughsubscript
superscripttabletable-cell
table-rowtargettimestamp
underlineverbatimverse-block

Here is an example filter that replaces non-breaking spaces ~ ~ in the Org buffer with ‘~’ for the LaTeX back-end.

(defun my-latex-filter-nobreaks (text backend info)
  "Ensure \" \" are properly handled in LaTeX export."
  (when (org-export-derived-backend-p backend 'latex)
    (replace-regexp-in-string " " "~" text)))

(add-to-list 'org-export-filter-plain-text-functions
             'my-latex-filter-nobreaks)

A filter requires three arguments: the code to be transformed, the name of the back-end, and some optional information about the export process. The third argument can be safely ignored. Note the use of org-export-derived-backend-p predicate that tests for latex back-end or any other back-end, such as beamer, derived from latex.

Defining filters for individual files

The Org export can filter not just for back-ends, but also for specific files through the ‘BIND’ keyword. Here is an example with two filters; one removes brackets from time stamps, and the other removes strike-through text. The filter functions are defined in a code block in the same Org file, which is a handy location for debugging.

#+BIND: org-export-filter-timestamp-functions (tmp-f-timestamp)
#+BIND: org-export-filter-strike-through-functions (tmp-f-strike-through)
#+BEGIN_SRC emacs-lisp :exports results :results none
  (defun tmp-f-timestamp (s backend info)
    (replace-regexp-in-string "&[lg]t;\\|[][]" "" s))
  (defun tmp-f-strike-through (s backend info) "")
#+END_SRC

Extending an existing back-end

Some parts of the conversion process can be extended for certain elements so as to introduce a new or revised translation. That is how the HTML export back-end was extended to handle Markdown format. The extensions work seamlessly so any aspect of filtering not done by the extended back-end is handled by the original back-end. Of all the export customization in Org, extending is very powerful as it operates at the parser level.

For this example, make the ascii back-end display the language used in a source code block. Also make it display only when some attribute is non-nil, like the following:

#+ATTR_ASCII: :language t

Then extend ASCII back-end with a custom “my-ascii” back-end.

(defun my-ascii-src-block (src-block contents info)
  "Transcode a SRC-BLOCK element from Org to ASCII.
CONTENTS is nil.  INFO is a plist used as a communication
channel."
  (if (not (org-export-read-attribute :attr_ascii src-block :language))
      (org-export-with-backend 'ascii src-block contents info)
    (concat
     (format ",--[ %s ]--\n%s`----"
             (org-element-property :language src-block)
             (replace-regexp-in-string
              "^" "| "
              (org-element-normalize-string
               (org-export-format-code-default src-block info)))))))

(org-export-define-derived-backend 'my-ascii 'ascii
  :translate-alist '((src-block . my-ascii-src-block)))

The my-ascii-src-block function looks at the attribute above the current element. If not true, hands over to ascii back-end. If true, which it is in this example, it creates a box around the code and leaves room for the inserting a string for language. The last form creates the new back-end that springs to action only when translating src-block type elements.

To use the newly defined back-end, evaluate the following from an Org buffer:

(org-export-to-buffer 'my-ascii "*Org MY-ASCII Export*")

Further steps to consider would be an interactive function, self-installing an item in the export dispatcher menu, and other user-friendly improvements.