13.17 Advanced Export Configuration

Export hooks

导出过程会在实际导出开始前执行两个钩子。第一个钩子 org-export-before-processing-functions 会在缓冲区中的宏、Babel 代码以及包含关键字展开之前运行。第二个钩子 org-export-before-parsing-functions 会在缓冲区被解析之前运行。

添加到这些钩子中的函数会以单个参数调用:实际使用的导出后端,以符号形式表示。你可以用它们对文档进行大规模的结构修改。例如,你可以在导出期间移除缓冲区中的所有标题,示例如下:

(defun my-headline-removal (backend)
  "Remove all headlines in the current buffer.
BACKEND is the export backend 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-functions #'my-headline-removal)

Filters

过滤器是应用于指定后端特定部分的函数列表。过滤器中第一个函数的输出会传递给下一个函数,最终输出为过滤器中最后一个函数的结果。

Org 导出过程拥有许多适用于不同类型对象、纯文本、语法树、导出选项和最终输出格式的过滤器集。过滤器以元素类型或对象类型命名:~org-export-filter-TYPE-functions~ ,其中 TYPE 为过滤器的目标类型。有效类型包括:

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

下面是一个过滤器示例,它会在 LaTeX 后端中将 Org 缓冲区中的非换行空格 ~ ~ 替换为 ‘~’ 。

(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)

过滤器需要三个参数:待转换的代码、后端名称以及一些关于导出过程的可选信息。第三个参数可以安全忽略。注意使用 org-export-derived-backend-p 谓词来判断 latex 后端或从 latex 派生的其他后端(如 beamer )。

Defining filters for individual files

Org 导出不仅可以针对后端设置过滤器,还可以通过 ‘BIND’ 关键字针对特定文件设置过滤器。下面是包含两个过滤器的示例:一个移除时间戳中的括号,另一个移除删除线文本。过滤器函数定义在同一 Org 文件的代码块中,这对调试十分方便。

#+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

Summary of the export process

Org 模式导出是一个多步骤过程,基于缓冲区的临时副本进行操作。导出过程包含 4 个主要步骤:

  1. 处理临时副本,对缓冲区文本进行必要修改;
  2. 解析缓冲区,将纯 Org 标记转换为抽象语法树(AST);
  3. 根据所选导出后端的规则,将 AST 转换为文本;
  4. 对最终导出的文本进行后处理。

处理源 Org 缓冲区的临时副本 151:

  1. 执行 org-export-before-processing-functions (见 Export hooks);
  2. 在整个缓冲区中展开 ‘#+include’ 关键字(见 Include Files);
  3. 在整个缓冲区中移除注释子树(见 Comment Lines);
  4. 在整个缓冲区中替换宏(见 Macro Replacement),除非 org-export-replace-macros 为 nil;
  5. org-export-use-babel 非空(默认值)时,处理代码块:

解析临时缓冲区,创建 AST:

  1. 执行 org-export-before-parsing-functions (见 Export hooks)。钩子函数仍可修改缓冲区;
  2. 根据子树专属导出设置、缓冲区内部关键字、 ‘#+BIND’ 关键字以及缓冲区局部和全局自定义项计算导出选项值。整个缓冲区都会被纳入考量;
  3. org-org-with-cite-processors 非空(默认值)时,确定相关参考文献并记录到导出选项中(见 Citations)。整个缓冲区都会被纳入考量;
  4. 执行 org-export-filter-options-functions
  5. 解析临时缓冲区的可访问部分以生成 AST。AST 是表示 Org 语法元素的嵌套列表(更多细节见 Org Element API):
    (org-data ...
     (heading
      (section
       (paragraph (plain-text) (bold (plain-text))))
      (heading)
      (heading (section ...))))
    

    在此步骤之后,对临时缓冲区的修改将不再影响导出;Org 导出仅基于 AST 进行操作;

  6. 从 AST 中移除不会被导出的元素:
    • 根据 ‘SELECT_TAGS’ 和 ‘EXCLUDE_TAGS’ 导出关键字、 ‘task’ 、 ‘inline’ 、 ‘arch’ 导出选项移除标题(见 Export Settings);
    • 注释内容;
    • 根据 ‘#+OPTIONS’ 关键字移除时钟、抽屉、固定宽度环境、脚注、LaTeX 环境与片段、节点属性、规划行、属性抽屉、统计标记、时间戳等(见 Export Settings);
    • 移除包含宽度和对齐标记的表格行,除非所选导出后端将 :with-special-rows 导出选项设为非空(见 Column Width and Alignment);
    • 移除包含重新计算标记的表格列(见 Advanced features)。
  7. 根据 ‘expand-links’ 导出选项展开文件链接 AST 节点中的环境变量(见 Export Settings);
  8. 执行 org-export-filter-parse-tree-functions 。这些函数可通过副作用修改 AST;
  9. org-org-with-cite-processors 非空(默认值)时,根据所选引用导出处理器的规则替换引用 AST 节点和 ‘#+print_bibliography’ 关键字 AST 节点(见 Citation export processors)。

通过深度优先遍历 AST 节点,将 AST 转换为文本:

  1. 将叶节点(无子节点)按照所选导出后端中 “转码器(transcoders)” 的规定转换为文本 152;
  2. 将转换后的节点通过对应的导出过滤器(见 Filters);
  3. 拼接所有转换后的子节点以生成父节点内容;
  4. 将包含子节点的节点转换为文本,先将节点自身及其导出内容传入对应转码器,再传入导出过滤器(见 Filters)。

对导出文本进行后处理:

  1. 按照导出后端的规定,对转换后的抽象语法树(AST)进行后处理。153 该步骤通常会向导出文本中添加生成内容(例如目录);
  2. 执行 org-export-filter-body-functions
  3. 除非选择仅导出正文(见 The Export Dispatcher),否则根据导出后端规则向最终文档添加必要的元数据。例如:文档作者/标题;HTML 头部/尾部;LaTeX 导言区;
  4. org-org-with-cite-processors 非空(默认值)时,根据引用导出处理器规则添加参考文献元数据;
  5. 执行 org-export-filter-final-output-functions

Extending an existing backend

转换过程的部分内容可针对特定元素进行扩展,以引入新的或修改后的转换规则。HTML 导出后端正是通过这种方式扩展以支持 Markdown 格式。扩展可无缝工作,扩展后端未处理的过滤内容将由原后端处理。在 Org 的所有导出自定义方式中,扩展功能十分强大,因为它在解析器层面运作。

在本示例中,让 ascii 后端显示源代码块中使用的语言,并且仅在某个属性为非 nil 时显示,如下所示:

#+ATTR_ASCII: :language t

然后基于 ASCII 后端扩展自定义的 “my-ascii” 后端。

(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)))

my-ascii-src-block 函数会读取当前元素上方的属性。若值为假,则交由 ascii 后端处理;若为真(本示例中即为真),则在代码周围创建框线并预留位置插入语言字符串。最后一段代码创建新后端,仅在转换 src-block 类型元素时生效。

要使用新定义的后端,在 Org 缓冲区中执行以下代码:

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

后续可考虑实现交互式函数、在导出调度器菜单中自动添加选项等用户友好的改进。更多细节见 https://orgmode.org/worg/dev/org-export-reference.html


Footnotes

(151)

除非另有说明,导出过程的每一步仅对缓冲区的可访问部分进行操作。当选择子树导出时(参见 The Export Dispatcher),缓冲区会被缩小到所选子树的主体范围,因此缓冲区中除导出关键字外的其余文本不会进入导出结果。

(152)

参见 org-export-define-backendorg-export-define-derived-backend 文档字符串中的转码器及 :translate-alist

(153)

参见 org-export-define-backend 文档字符串中的 inner-template