From 4e2aaadda9c8c70320b41cd2c6402951a727d278 Mon Sep 17 00:00:00 2001 From: Nicolas Goaziou Date: Fri, 25 Nov 2011 16:02:27 +0100 Subject: [PATCH] contrib/org-export: Generic Export Engine For Org * contrib/lisp/org-export.el (org-export-collect-options, org-export-parse-option-keyword, org-export-get-subtree-options, org-export-get-inbuffer-options, org-export-get-global-options, org-export-initial-options, org-export-confirm-letbind, org-export-install-letbind-maybe, org-export-initialize-persistent-properties, org-export-use-select-tags-p, org-export-get-min-level, org-export-get-point-max, org-export-update-info, org-export-set-property, org-export-data, org-export-secondary-string, org-export-skip-p, org-export-interpret-p, org-export-filter-apply-functions, org-export-as, org-export-to-buffer, org-export-get-relative-level, org-export-get-headline-number, org-export-number-to-roman, org-export-first-sibling-p, org-export-last-sibling-p, org-export-included-file, org-export-get-file-contents, org-export-parse-included-file, org-export-solidify-link-text, org-export-get-coderef-format, org-export-inline-image-p, org-export-resolve-fuzzy-link, org-export-expand-macro, org-export-handle-code, org-export-table-format-info, org-export-clean-table, org-export-get-headlines, org-export-collect-elements, org-export-collect-tables, org-export-get-figures, org-export-collect-listings): New functions. (org-export-max-depth, org-export-option-alist, org-export-special-keywords, org-export-persistent-properties-list, org-export-persistent-properties, org-export-with-archived-trees, org-export-with-author, org-export-with-creator, org-export-creator-string, org-export-with-drawers, org-export-with-email, org-export-with-emphasize, org-export-exclude-tags, org-export-with-fixed-width, org-export-with-footnotes, org-export-headline-levels, org-export-default-language, org-export-preserve-breaks, org-export-with-entities, org-export-with-priority, org-export-with-section-numbers, org-export-select-tags, org-export-with-special-strings, org-export-with-sub-superscripts, org-export-with-toc, org-export-with-tables, org-export-with-tags, org-export-with-tasks, org-export-time-stamp-file, org-export-with-timestamps, org-export-with-todo-keywords, org-export-allow-BIND, org-export-snippet-translation-alist, org-export-filter-parse-tree-functions, org-export-filter-final-output-functions, org-export-filter-plain-text-functions, org-export-filter-center-block-functions, org-export-filter-drawer-functions, org-export-filter-dynamic-block-functions, org-export-filter-headline-functions, org-export-filter-inlinetask-functions, org-export-filter-plain-list-functions, org-export-filter-item-functions, org-export-filter-comment-functions, org-export-filter-comment-block-functions, org-export-filter-example-block-functions, org-export-filter-export-block-functions, org-export-filter-fixed-width-functions, org-export-filter-footnote-definition-functions, org-export-filter-horizontal-rule-functions, org-export-filter-keyword-functions, org-export-filter-latex-environment-functions, org-export-filter-babel-call-functions, org-export-filter-paragraph-functions, org-export-filter-property-drawer-functions, org-export-filter-quote-block-functions, org-export-filter-quote-section-functions, org-export-filter-special-block-functions, org-export-filter-src-block-functions, org-export-filter-table-functions, org-export-filter-verse-block-functions, org-export-filter-emphasis-functions, org-export-filter-entity-functions, org-export-filter-export-snippet-functions, org-export-filter-footnote-reference-functions, org-export-filter-inline-babel-call-functions, org-export-filter-inline-src-block-functions, org-export-filter-latex-fragment-functions, org-export-filter-line-break-functions, org-export-filter-link-functions, org-export-filter-macro-functions, org-export-filter-radio-target-functions, org-export-filter-statistics-cookie-functions, org-export-filter-subscript-functions, org-export-filter-superscript-functions, org-export-filter-target-functions, org-export-filter-time-stamp-functions, org-export-filter-verbatim-functions): New variables. --- contrib/lisp/org-export.el | 2645 ++++++++++++++++++++++++++++++++++++ 1 file changed, 2645 insertions(+) create mode 100644 contrib/lisp/org-export.el diff --git a/contrib/lisp/org-export.el b/contrib/lisp/org-export.el new file mode 100644 index 000000000..c9b435f71 --- /dev/null +++ b/contrib/lisp/org-export.el @@ -0,0 +1,2645 @@ +;;; org-export.el --- Generic Export Engine For Org + +;; Copyright (C) 2011 Free Software Foundation, Inc. + +;; Author: Nicolas Goaziou +;; Keywords: outlines, hypermedia, calendar, wp + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; This library implements a generic export engine for Org, built on +;; its syntactical parser: Org Elements. + +;; Besides that parser, the generic exporter is made of three distinct +;; parts: + +;; - The communication channel consists in a property list, which is +;; created and updated during the process. Its use is to offer +;; every piece of information, would it be export options or +;; contextual data, all in a single place. The exhaustive list of +;; properties is given in "The Communication Channel" section of +;; this file. + +;; - The transcoder walks the parse tree, ignores or treat as plain +;; text elements and objects according to export options, and +;; eventually calls back-end specific functions to do the real +;; transcoding, concatenating their return value along the way. + +;; - The filter system is activated at the very beginning and the very +;; end of the export process, and each time an element or an object +;; has been converted. It is the entry point to fine-tune standard +;; output from back-end transcoders. + +;; The core function is `org-export-as'. It returns the transcoded +;; buffer as a string. + +;; In order to derive an exporter out of this generic implementation, +;; one can define a transcode function for each element or object. +;; Such function should return a string for the corresponding element, +;; without any trailing space, or nil. It must accept three +;; arguments: +;; 1. the element or object itself, +;; 2. its contents, or nil when it isn't recursive, +;; 3. the property list used as a communication channel. + +;; If no such function is found, that element or object type will +;; simply be ignored, along with any separating blank line. The same +;; will happen if the function returns the nil value. If that +;; function returns the empty string, the type will be ignored, but +;; the blank lines will be kept. + +;; Contents, when not nil, are stripped from any global indentation +;; (although the relative one is preserved). They also always end +;; with a single newline character. + +;; These functions must follow a strict naming convention: +;; `org-element-BACKEND-TYPE' where, obviously, BACKEND is the name of +;; the export back-end and TYPE the type of the element or object +;; handled. + +;; Moreover, two additional functions can be defined. On the one +;; hand, `org-BACKEND-template' returns the final transcoded string, +;; and can be used to add a preamble and a postamble to document's +;; body. It must accept two arguments: the transcoded string and the +;; property list containing export options. On the other hand, +;; `org-BACKEND-plain-text', when defined, is to be called on every +;; text not recognized as an element or an object. It must accept two +;; arguments: the text string and the information channel. + +;; Any back-end can define its own variables. Among them, those +;; customizables should belong to the `org-export-BACKEND' group. +;; Also, a special variable, `org-BACKEND-option-alist', allows to +;; define buffer keywords and "#+options:" items specific to that +;; back-end. See `org-export-option-alist' for supported defaults and +;; syntax. + +;; Tools for common tasks across back-ends are implemented in the last +;; part of this file. + +;;; Code: +(eval-when-compile (require 'cl)) +(require 'org-element) + + +;;; Internal Variables + +;; Among internal variables, the most important is +;; `org-export-option-alist'. This variable define the global export +;; options, shared between every exporter, and how they are acquired. + +(defconst org-export-max-depth 19 + "Maximum nesting depth for headlines, counting from 0.") + +(defconst org-export-option-alist + '((:author "AUTHOR" nil user-full-name t) + (:creator "CREATOR" nil org-export-creator-string) + (:date "DATE" nil nil t) + (:description "DESCRIPTION" nil nil newline) + (:email "EMAIL" nil user-mail-address t) + (:exclude-tags "EXPORT_EXCLUDE_TAGS" nil org-export-exclude-tags split) + (:headline-levels nil "H" org-export-headline-levels) + (:keywords "KEYWORDS" nil nil space) + (:language "LANGUAGE" nil org-export-default-language t) + (:preserve-breaks nil "\\n" org-export-preserve-breaks) + (:section-numbers nil "num" org-export-with-section-numbers) + (:select-tags "EXPORT_SELECT_TAGS" nil org-export-select-tags split) + (:time-stamp-file nil "timestamp" org-export-time-stamp-file) + (:title "TITLE" nil nil space) + (:with-archived-trees nil "arch" org-export-with-archived-trees) + (:with-author nil "author" org-export-with-author) + (:with-creator nil "creator" org-export-with-creator) + (:with-drawers nil "drawer" org-export-with-drawers) + (:with-email nil "email" org-export-with-email) + (:with-emphasize nil "*" org-export-with-emphasize) + (:with-entities nil "e" org-export-with-entities) + (:with-fixed-width nil ":" org-export-with-fixed-width) + (:with-footnotes nil "f" org-export-with-footnotes) + (:with-priority nil "pri" org-export-with-priority) + (:with-special-strings nil "-" org-export-with-special-strings) + (:with-sub-superscript nil "^" org-export-with-sub-superscripts) + (:with-toc nil "toc" org-export-with-toc) + (:with-tables nil "|" org-export-with-tables) + (:with-tags nil "tags" org-export-with-tags) + (:with-tasks nil "tasks" org-export-with-tasks) + (:with-timestamps nil "<" org-export-with-timestamps) + (:with-todo-keywords nil "todo" org-export-with-todo-keywords)) + "Alist between export properties and ways to set them. + +The car of the alist is the property name, and the cdr is a list +like \(KEYWORD OPTION DEFAULT BEHAVIOUR\) where: + +KEYWORD is a string representing a buffer keyword, or nil. +OPTION is a string that could be found in an #+OPTIONS: line. +DEFAULT is the default value for the property. +BEHAVIOUR determine how Org should handle multiple keywords for +the same property. It is a symbol among: + nil Keep old value and discard the new one. + t Replace old value with the new one. + `space' Concatenate the values, separating them with a space. + `newline' Concatenate the values, separating them with + a newline. + `split' Split values at white spaces, and cons them to the + previous list. + +KEYWORD and OPTION have precedence over VARIABLE. + +All these properties should be back-end agnostic. For back-end +specific properties, define a similar variable named +`org-element-BACKEND-option-alist', replacing BACKEND with the +name of the appropriate back-end. You can also redefine +properties there, as they have precedence over these.") + +(defconst org-export-special-keywords + '("SETUP_FILE" "OPTIONS" "MACRO") + "List of in-buffer keywords that require special treatment. +These keywords are not directly associated to a property. The +way they are handled must be hard-coded into +`org-export-get-inbuffer-options' function.") + + + +;;; User-configurable Variables + +;; Configuration for the masses. + +;; They should never be evaled directly, as their value is to be +;; stored in a property list (cf. `org-export-option-alist'). + +(defgroup org-export nil + "Options for exporting Org mode files." + :tag "Org Export" + :group 'org) + +(defgroup org-export-general nil + "General options for export engine." + :tag "Org Export General" + :group 'org-export) + +(defcustom org-export-with-archived-trees 'headline + "Whether sub-trees with the ARCHIVE tag should be exported. + +This can have three different values: +nil Do not export, pretend this tree is not present. +t Do export the entire tree. +`headline' Only export the headline, but skip the tree below it. + +This option can also be set with the #+OPTIONS line, +e.g. \"arch:nil\"." + :group 'org-export-general + :type '(choice + (const :tag "Not at all" nil) + (const :tag "Headline only" 'headline) + (const :tag "Entirely" t))) + +(defcustom org-export-with-author t + "Non-nil means insert author name into the exported file. +This option can also be set with the #+OPTIONS line, +e.g. \"author:nil\"." + :group 'org-export-general + :type 'boolean) + +(defcustom org-export-with-creator 'comment + "Non-nil means the postamble should contain a creator sentence. + +The sentence can be set in `org-export-creator-string' and +defaults to \"Generated by Org mode XX in Emacs XXX.\". + +If the value is `comment' insert it as a comment." + :group 'org-export-general + :type '(choice + (const :tag "No creator sentence" nil) + (const :tag "Sentence as a comment" 'comment) + (const :tag "Insert the sentence" t))) + +(defcustom org-export-creator-string + (format "Generated by Org mode %s in Emacs %s." org-version emacs-version) + "String to insert at the end of the generated document." + :group 'org-export-general + :type '(string :tag "Creator string")) + +(defcustom org-export-with-drawers nil + "Non-nil means export with drawers like the property drawer. +When t, all drawers are exported. This may also be a list of +drawer names to export." + :group 'org-export-general + :type '(choice + (const :tag "All drawers" t) + (const :tag "None" nil) + (repeat :tag "Selected drawers" + (string :tag "Drawer name")))) + +(defcustom org-export-with-email nil + "Non-nil means insert author email into the exported file. +This option can also be set with the #+OPTIONS line, +e.g. \"email:t\"." + :group 'org-export-general + :type 'boolean) + +(defcustom org-export-with-emphasize t + "Non-nil means interpret *word*, /word/, and _word_ as emphasized text. + +If the export target supports emphasizing text, the word will be +typeset in bold, italic, or underlined, respectively. Not all +export backends support this. + +This option can also be set with the #+OPTIONS line, e.g. \"*:nil\"." + :group 'org-export-general + :type 'boolean) + +(defcustom org-export-exclude-tags '("noexport") + "Tags that exclude a tree from export. +All trees carrying any of these tags will be excluded from +export. This is without condition, so even subtrees inside that +carry one of the `org-export-select-tags' will be removed." + :group 'org-export-general + :type '(repeat (string :tag "Tag"))) + +(defcustom org-export-with-fixed-width t + "Non-nil means lines starting with \":\" will be in fixed width font. + +This can be used to have pre-formatted text, fragments of code +etc. For example: + : ;; Some Lisp examples + : (while (defc cnt) + : (ding)) +will be looking just like this in also HTML. See also the QUOTE +keyword. Not all export backends support this. + +This option can also be set with the #+OPTIONS line, e.g. \"::nil\"." + :group 'org-export-translation + :type 'boolean) + +(defcustom org-export-with-footnotes t + "Non-nil means Org footnotes should be exported. +This option can also be set with the #+OPTIONS line, +e.g. \"f:nil\"." + :group 'org-export-general + :type 'boolean) + +(defcustom org-export-headline-levels 3 + "The last level which is still exported as a headline. + +Inferior levels will produce itemize lists when exported. Note +that a numeric prefix argument to an exporter function overrides +this setting. + +This option can also be set with the #+OPTIONS line, e.g. \"H:2\"." + :group 'org-export-general + :type 'integer) + +(defcustom org-export-default-language "en" + "The default language for export and clocktable translations, as a string. +This may have an association in +`org-clock-clocktable-language-setup'." + :group 'org-export-general + :type '(string :tag "Language")) + +(defcustom org-export-preserve-breaks nil + "Non-nil means preserve all line breaks when exporting. + +Normally, in HTML output paragraphs will be reformatted. + +This option can also be set with the #+OPTIONS line, +e.g. \"\\n:t\"." + :group 'org-export-general + :type 'boolean) + +(defcustom org-export-with-entities t + "Non-nil means interpret entities when exporting. + +For example, HTML export converts \\alpha to α and \\AA to +Å. + +For a list of supported names, see the constant `org-entities' +and the user option `org-entities-user'. + +This option can also be set with the #+OPTIONS line, +e.g. \"e:nil\"." + :group 'org-export-general + :type 'boolean) + +(defcustom org-export-with-priority nil + "Non-nil means include priority cookies in export. +When nil, remove priority cookies for export." + :group 'org-export-general + :type 'boolean) + +(defcustom org-export-with-section-numbers t + "Non-nil means add section numbers to headlines when exporting. + +This option can also be set with the #+OPTIONS line, +e.g. \"num:t\"." + :group 'org-export-general + :type 'boolean) + +(defcustom org-export-select-tags '("export") + "Tags that select a tree for export. +If any such tag is found in a buffer, all trees that do not carry +one of these tags will be deleted before export. Inside trees +that are selected like this, you can still deselect a subtree by +tagging it with one of the `org-export-exclude-tags'." + :group 'org-export-general + :type '(repeat (string :tag "Tag"))) + +(defcustom org-export-with-special-strings t + "Non-nil means interpret \"\-\", \"--\" and \"---\" for export. + +When this option is turned on, these strings will be exported as: + + Org HTML LaTeX + -----+----------+-------- + \\- ­ \\- + -- – -- + --- — --- + ... … \ldots + +This option can also be set with the #+OPTIONS line, +e.g. \"-:nil\"." + :group 'org-export-general + :type 'boolean) + +(defcustom org-export-with-sub-superscripts t + "Non-nil means interpret \"_\" and \"^\" for export. + +When this option is turned on, you can use TeX-like syntax for +sub- and superscripts. Several characters after \"_\" or \"^\" +will be considered as a single item - so grouping with {} is +normally not needed. For example, the following things will be +parsed as single sub- or superscripts. + + 10^24 or 10^tau several digits will be considered 1 item. + 10^-12 or 10^-tau a leading sign with digits or a word + x^2-y^3 will be read as x^2 - y^3, because items are + terminated by almost any nonword/nondigit char. + x_{i^2} or x^(2-i) braces or parenthesis do grouping. + +Still, ambiguity is possible - so when in doubt use {} to enclose +the sub/superscript. If you set this variable to the symbol +`{}', the braces are *required* in order to trigger +interpretations as sub/superscript. This can be helpful in +documents that need \"_\" frequently in plain text. + +This option can also be set with the #+OPTIONS line, +e.g. \"^:nil\"." + :group 'org-export-general + :type '(choice + (const :tag "Interpret them" t) + (const :tag "Curly brackets only" {}) + (const :tag "Do not interpret them" nil))) + +(defcustom org-export-with-toc t + "Non-nil means create a table of contents in exported files. + +The TOC contains headlines with levels up +to`org-export-headline-levels'. When an integer, include levels +up to N in the toc, this may then be different from +`org-export-headline-levels', but it will not be allowed to be +larger than the number of headline levels. When nil, no table of +contents is made. + +This option can also be set with the #+OPTIONS line, +e.g. \"toc:nil\" or \"toc:3\"." + :group 'org-export-general + :type '(choice + (const :tag "No Table of Contents" nil) + (const :tag "Full Table of Contents" t) + (integer :tag "TOC to level"))) + +(defcustom org-export-with-tables t + "If non-nil, lines starting with \"|\" define a table. +For example: + + | Name | Address | Birthday | + |-------------+----------+-----------| + | Arthur Dent | England | 29.2.2100 | + +This option can also be set with the #+OPTIONS line, e.g. \"|:nil\"." + :group 'org-export-general + :type 'boolean) + +(defcustom org-export-with-tags t + "If nil, do not export tags, just remove them from headlines. + +If this is the symbol `not-in-toc', tags will be removed from +table of contents entries, but still be shown in the headlines of +the document. + +This option can also be set with the #+OPTIONS line, +e.g. \"tags:nil\"." + :group 'org-export-general + :type '(choice + (const :tag "Off" nil) + (const :tag "Not in TOC" not-in-toc) + (const :tag "On" t))) + +(defcustom org-export-with-tasks t + "Non-nil means include TODO items for export. +This may have the following values: +t include tasks independent of state. +todo include only tasks that are not yet done. +done include only tasks that are already done. +nil remove all tasks before export +list of keywords keep only tasks with these keywords" + :group 'org-export-general + :type '(choice + (const :tag "All tasks" t) + (const :tag "No tasks" nil) + (const :tag "Not-done tasks" todo) + (const :tag "Only done tasks" done) + (repeat :tag "Specific TODO keywords" + (string :tag "Keyword")))) + +(defcustom org-export-time-stamp-file t + "Non-nil means insert a time stamp into the exported file. +The time stamp shows when the file was created. + +This option can also be set with the #+OPTIONS line, +e.g. \"timestamp:nil\"." + :group 'org-export-general + :type 'boolean) + +(defcustom org-export-with-timestamps t + "If nil, do not export time stamps and associated keywords." + :group 'org-export-general + :type 'boolean) + +(defcustom org-export-with-todo-keywords t + "Non-nil means include TODO keywords in export. +When nil, remove all these keywords from the export.") + +(defcustom org-export-allow-BIND 'confirm + "Non-nil means allow #+BIND to define local variable values for export. +This is a potential security risk, which is why the user must +confirm the use of these lines." + :group 'org-export-general + :type '(choice + (const :tag "Never" nil) + (const :tag "Always" t) + (const :tag "Ask a confirmation for each file" confirm))) + +(defcustom org-export-snippet-translation-alist nil + "Alist between export snippets back-ends and exporter back-ends. + +This variable allows to provide shortcuts for export snippets. + +For example, with a value of '\(\(\"h\" . \"html\"\)\), the HTML +back-end will recognize the contents of \"@h{}\" as HTML code +while every other back-end will ignore it." + :group 'org-export-general + :type '(repeat + (cons + (string :tag "Shortcut") + (string :tag "Back-end")))) + + + +;;; The Communication Channel + +;; During export process, every function has access to a number of +;; properties. They are of three types: + +;; 1. Export options are collected once at the very beginning of the +;; process, out of the original buffer and environment. The task +;; is handled by `org-export-collect-options' function. +;; +;; All export options are defined through the +;; `org-export-option-alist' variable. +;; +;; 2. Persistent properties are stored in +;; `org-export-persistent-properties' and available at every level +;; of recursion. Their value is extracted directly from the parsed +;; tree, and depends on export options (whole trees may be filtered +;; out of the export process). +;; +;; Properties belonging to that type are defined in the +;; `org-export-persistent-properties-list' variable. +;; +;; 3. Every other property is considered local, and available at +;; a precise level of recursion and below. + +;; Managing properties during transcode process is mainly done with +;; `org-export-update-info'. Even though they come from different +;; sources, the function transparently concatenates them in a single +;; property list passed as an argument to each transcode function. +;; Thus, during export, all necessary information is available through +;; that single property list, and the element or object itself. +;; Though, modifying a property will still require some special care, +;; and should be done with `org-export-set-property' instead of plain +;; `plist-put'. + +;; Here is the full list of properties available during transcode +;; process, with their category (option, persistent or local), their +;; value type and the function updating them, when appropriate. + +;; + `author' :: Author's name. +;; - category :: option +;; - type :: string + +;; + `back-end' :: Current back-end used for transcoding. +;; - category :: persistent +;; - type :: symbol + +;; + `code-refs' :: Association list between reference name and real +;; labels in source code. It is used to properly +;; resolve links inside source blocks. +;; - category :: persistent +;; - type :: alist (INT-OR-STRING . STRING) +;; - update :: `org-export-handle-code' + +;; + `creator' :: String to write as creation information. +;; - category :: option +;; - type :: string + +;; + `date' :: String to use as date. +;; - category :: option +;; - type :: string + +;; + `description' :: Description text for the current data. +;; - category :: option +;; - type :: string + +;; + `email' :: Author's email. +;; - category :: option +;; - type :: string + +;; + `exclude-tags' :: Tags for exclusion of subtrees from export +;; process. +;; - category :: option +;; - type :: list of strings + +;; + `footnotes-labels-alist' :: Alist between footnote labels and +;; their definition, as parsed data. Once retrieved, the +;; definition should be exported with `org-export-data'. +;; - category :: option +;; - type :: alist (STRING . LIST) + +;; + `genealogy' :: List of current element's parents types. +;; - category :: local +;; - type :: list of symbols +;; - update :: `org-export-update-info' + +;; + `headline-alist' :: Alist between headlines raw name and their +;; boundaries. It is used to resolve "fuzzy" links +;; (cf. `org-export-resolve-fuzzy-link'). +;; - category :: persistent +;; - type :: alist (STRING INTEGER INTEGER) + +;; + `headline-levels' :: Maximum level being exported as an +;; headline. Comparison is done with the relative level of +;; headlines in the parse tree, not necessarily with their +;; actual level. +;; - category :: option +;; - type :: integer + +;; + `headline-offset' :: Difference between relative and real level +;; of headlines in the parse tree. For example, a value of -1 +;; means a level 2 headline should be considered as level +;; 1 (cf. `org-export-get-relative-level'). +;; - category :: persistent +;; - type :: integer + +;; + `included-files' :: List of files, with full path, included in +;; the current buffer, through the "#+include:" keyword. It is +;; mainly used to verify that no infinite recursive inclusion +;; happens. +;; - category :: persistent +;; - type :: list of strings + +;; + `inherited-properties' :: Properties of the headline ancestors +;; of the current element or object. Those from the closest +;; headline have precedence over the others. +;; - category :: local +;; - type :: plist + +;; + `keywords' :: List of keywords attached to data. +;; - category :: option +;; - type :: string + +;; + `language' :: Default language used for translations. +;; - category :: option +;; - type :: string + +;; + `parent-properties' :: Properties of the parent element. +;; - category :: local +;; - type :: plist +;; - update :: `org-export-update-info' + +;; + `parse-tree' :: Whole parse tree, available at any time during +;; transcoding. +;; - category :: global +;; - type :: list (as returned by `org-element-parse-buffer') + +;; + `point-max' :: Last ending position in the parse tree. +;; - category :: global +;; - type :: integer + +;; + `preserve-breaks' :: Non-nil means transcoding should preserve +;; all line breaks. +;; - category :: option +;; - type :: symbol (nil, t) + +;; + `previous-element' :: Previous element's type at the same +;; level. +;; - category :: local +;; - type :: symbol +;; - update :: `org-export-update-info' + +;; + `previous-object' :: Previous object type (or `plain-text') at +;; the same level. +;; - category :: local +;; - type :: symbol +;; - update :: `org-export-update-info' + +;; + `previous-section-number' :: Numbering of the previous +;; headline. As it might not be practical for direct use, the +;; function `org-export-get-headline-level' is provided +;; to extract useful information out of it. +;; - category :: local +;; - type :: vector + +;; + `section-numbers' :: Non-nil means transcoding should add +;; section numbers to headlines. +;; - category :: option +;; - type :: symbol (nil, t) + +;; + `seen-footnote-labels' :: List of already transcoded footnote +;; labels. +;; - category :: persistent +;; - type :: list of strings +;; - update :: `org-export-update-info' + +;; + `select-tags' :: List of tags enforcing inclusion of sub-trees in +;; transcoding. When such a tag is present, +;; subtrees without it are de facto excluded from +;; the process. See `use-select-tags'. +;; - category :: option +;; - type :: list of strings + +;; + `target-list' :: List of targets raw names encoutered in the +;; parse tree. This is used to partly resolve +;; "fuzzy" links +;; (cf. `org-export-resolve-fuzzy-link'). +;; - category :: persistent +;; - type :: list of strings + +;; + `time-stamp-file' :: Non-nil means transcoding should insert +;; a time stamp in the output. +;; - category :: option +;; - type :: symbol (nil, t) + +;; + `total-loc' :: Contains total lines of code accumulated by source +;; blocks with the "+n" option so far. +;; - category :: option +;; - type :: integer +;; - update :: `org-export-handle-code' + +;; + `use-select-tags' :: When non-nil, a select tags has been found +;; in the parse tree. Thus, any headline without one will be +;; filtered out. See `select-tags'. +;; - category :: persistent +;; - type :: interger or nil + +;; + `with-archived-trees' :: Non-nil when archived subtrees should +;; also be transcoded. If it is set to the `headline' symbol, +;; only the archived headline's name is retained. +;; - category :: option +;; - type :: symbol (nil, t, `headline') + +;; + `with-author' :: Non-nil means author's name should be included +;; in the output. +;; - category :: option +;; - type :: symbol (nil, t) + +;; + `with-creator' :: Non-nild means a creation sentence should be +;; inserted at the end of the transcoded string. If the value +;; is `comment', it should be commented. +;; - category :: option +;; - type :: symbol (`comment', nil, t) + +;; + `with-drawers' :: Non-nil means drawers should be exported. If +;; its value is a list of names, only drawers with such names +;; will be transcoded. +;; - category :: option +;; - type :: symbol (nil, t) or list of strings + +;; + `with-email' :: Non-nil means output should contain author's +;; email. +;; - category :: option +;; - type :: symbol (nil, t) + +;; + `with-emphasize' :: Non-nil means emphasized text should be +;; interpreted. +;; - category :: option +;; - type :: symbol (nil, t) + +;; + `with-fixed-width' :: Non-nil if transcoder should interpret +;; strings starting with a colon as a fixed-with (verbatim) +;; area. +;; - category :: option +;; - type :: symbol (nil, t) + +;; + `with-footnotes' :: Non-nil if transcoder should interpret +;; footnotes. +;; - category :: option +;; - type :: symbol (nil, t) + +;; + `with-priority' :: Non-nil means transcoding should include +;; priority cookies. +;; - category :: option +;; - type :: symbol (nil, t) + +;; + `with-special-strings' :: Non-nil means transcoding should +;; interpret special strings in plain text. +;; - category :: option +;; - type :: symbol (nil, t) + +;; + `with-sub-superscript' :: Non-nil means transcoding should +;; interpret subscript and superscript. With a value of "{}", +;; only interpret those using curly brackets. +;; - category :: option +;; - type :: symbol (nil, {}, t) + +;; + `with-tables' :: Non-nil means transcoding should interpret +;; tables. +;; - category :: option +;; - type :: symbol (nil, t) + +;; + `with-tags' :: Non-nil means transcoding should keep tags in +;; headlines. A `not-in-toc' value will remove them +;; from the table of contents, if any, nonetheless. +;; - category :: option +;; - type :: symbol (nil, t, `not-in-toc') + +;; + `with-tasks' :: Non-nil means transcoding should include +;; headlines with a TODO keyword. A `todo' value +;; will only include headlines with a todo type +;; keyword while a `done' value will do the +;; contrary. If a list of strings is provided, only +;; tasks with keywords belonging to that list will +;; be kept. +;; - category :: option +;; - type :: symbol (t, todo, done, nil) or list of strings + +;; + `with-timestamps' :: Non-nil means transcoding should include +;; time stamps and associated keywords. Otherwise, completely +;; remove them. +;; - category :: option +;; - type :: symbol: (t, nil) + +;; + `with-toc' :: Non-nil means that a table of contents has to be +;; added to the output. An integer value limits its +;; depth. +;; - category :: option +;; - type :: symbol (nil, t or integer) + +;; + `with-todo-keywords' :: Non-nil means transcoding should +;; include TODO keywords. +;; - category :: option +;; - type :: symbol (nil, t) + +;;;; Export Options + +;; Export options come from five sources, in increasing precedence +;; order: + +;; - Global variables, +;; - External options provided at export time, +;; - Options keyword symbols, +;; - Buffer keywords, +;; - Subtree properties. + +;; The central internal function with regards to export options is +;; `org-export-collect-options'. It updates global variables with +;; "#+BIND:" keywords, then retrieve and prioritize properties from +;; the different sources. + +;; The internal functions doing the retrieval are: +;; `org-export-parse-option-keyword' , +;; `org-export-get-subtree-options' , +;; `org-export-get-inbuffer-options' and +;; `org-export-get-global-options'. +;; +;; Some properties do not rely on the previous sources but still +;; depend on the original buffer are taken care of in +;; `org-export-initial-options'. + +;; Also, `org-export-confirm-letbind' and `org-export-install-letbind' +;; take care of the part relative to "#+BIND:" keywords. + +(defun org-export-collect-options (backend subtreep ext-plist) + "Collect export options from the current buffer. + +BACKEND is a symbol specifying the back-end to use. + +When SUBTREEP is non-nil, assume the export is done against the +current sub-tree. + +EXT-PLIST is a property list with external parameters overriding +org-mode's default settings, but still inferior to file-local +settings." + ;; First install #+BIND variables. + (org-export-install-letbind-maybe) + ;; Get and prioritize export options... + (let ((options (org-combine-plists + ;; ... from global variables... + (org-export-get-global-options backend) + ;; ... from an external property list... + ext-plist + ;; ... from in-buffer settings... + (org-export-get-inbuffer-options + (org-with-wide-buffer (buffer-string)) backend + (and buffer-file-name + (org-remove-double-quotes buffer-file-name))) + ;; ... and from subtree, when appropriate. + (and subtreep + (org-export-get-subtree-options))))) + ;; Add initial options. + (setq options (append (org-export-initial-options options) + options)) + ;; Set a default title if none has been specified so far. + (unless (plist-get options :title) + (setq options (plist-put options :title + (or (and buffer-file-name + (file-name-sans-extension + (file-name-nondirectory + buffer-file-name))) + (buffer-name))))) + ;; Return plist. + options)) + +(defun org-export-parse-option-keyword (options backend) + "Parse an OPTIONS line and return values as a plist. +BACKEND is a symbol specifying the back-end to use." + (let* ((all (append org-export-option-alist + (let ((var (intern + (format "org-%s-option-alist" backend)))) + (and (boundp var) (eval var))))) + ;; Build an alist between #+OPTION: item and property-name. + (alist (delq nil + (mapcar (lambda (e) + (when (nth 2 e) (cons (regexp-quote (nth 2 e)) + (car e)))) + all))) + plist) + (mapc (lambda (e) + (when (string-match (concat "\\(\\`\\|[ \t]\\)" + (car e) + ":\\(([^)\n]+)\\|[^ \t\n\r;,.]*\\)") + options) + (setq plist (plist-put plist + (cdr e) + (car (read-from-string + (match-string 2 options))))))) + alist) + plist)) + +(defun org-export-get-subtree-options () + "Get export options in subtree at point. +Return the options as a plist." + (org-with-wide-buffer + (when (ignore-errors (org-back-to-heading t)) + (let (prop plist) + (when (setq prop (progn (looking-at org-todo-line-regexp) + (or (org-entry-get (point) "EXPORT_TITLE") + (org-match-string-no-properties 3)))) + (setq plist (plist-put plist :title prop))) + (when (setq prop (org-entry-get (point) "EXPORT_TEXT")) + (setq plist (plist-put plist :text prop))) + (when (setq prop (org-entry-get (point) "EXPORT_AUTHOR")) + (setq plist (plist-put plist :author prop))) + (when (setq prop (org-entry-get (point) "EXPORT_DATE")) + (setq plist (plist-put plist :date prop))) + (when (setq prop (org-entry-get (point) "EXPORT_OPTIONS")) + (setq plist (org-export-add-options-to-plist plist prop))) + plist)))) + +(defun org-export-get-inbuffer-options (buffer-string backend files) + "Return in-buffer options as a plist. +BUFFER-STRING is the string of the buffer. BACKEND is a symbol +specifying which back-end should be used." + (let ((case-fold-search t) plist) + ;; 1. Special keywords, as in `org-export-special-keywords'. + (let ((start 0) + (special-re (org-make-options-regexp org-export-special-keywords))) + (while (string-match special-re buffer-string start) + (setq start (match-end 0)) + (let ((key (upcase (org-match-string-no-properties 1 buffer-string))) + ;; Special keywords do not have their value expanded. + (val (org-match-string-no-properties 2 buffer-string))) + (setq plist + (org-combine-plists + (cond + ((string= key "SETUP_FILE") + (let ((file (expand-file-name + (org-remove-double-quotes (org-trim val))))) + ;; Avoid circular dependencies. + (unless (member file files) + (org-export-get-inbuffer-options + (org-file-contents file 'noerror) + backend + (cons file files))))) + ((string= key "OPTIONS") + (org-export-parse-option-keyword val backend)) + ((string= key "MACRO") + (string-match "^\\([-a-zA-Z0-9_]+\\)[ \t]+\\(.*?[ \t]*$\\)" + val) + (plist-put nil + (intern (concat ":macro-" + (downcase (match-string 1 val)))) + (match-string 2 val)))) + plist))))) + ;; 2. Standard options, as in `org-export-option-alist'. + (let* ((all (append org-export-option-alist + (let ((var (intern + (format "org-%s-option-alist" backend)))) + (and (boundp var) (eval var))))) + ;; Build alist between keyword name and property name. + (alist (delq nil (mapcar (lambda (e) + (when (nth 1 e) (cons (nth 1 e) (car e)))) + all))) + ;; Build regexp matching all keywords associated to export + ;; options. Note: the search is case insensitive. + (opt-re (org-make-options-regexp + (delq nil (mapcar (lambda (e) (nth 1 e)) all)))) + (start 0)) + (while (string-match opt-re buffer-string start) + (setq start (match-end 0)) + (let* ((key (upcase (org-match-string-no-properties 1 buffer-string))) + ;; Expand value, applying restrictions for keywords. + (val (org-match-string-no-properties 2 buffer-string)) + (prop (cdr (assoc key alist))) + (behaviour (nth 4 (assq prop all)))) + (setq plist + (plist-put + plist prop + ;; Handle value depending on specified BEHAVIOUR. + (case behaviour + (space (if (plist-get plist prop) + (concat (plist-get plist prop) " " (org-trim val)) + (org-trim val))) + (newline (org-trim + (concat + (plist-get plist prop) "\n" (org-trim val)))) + (split `(,@(plist-get plist prop) ,@(org-split-string val))) + ('t val) + (otherwise (plist-get plist prop))))))) + ;; Parse keywords specified in `org-element-parsed-keywords'. + (mapc + (lambda (key) + (let* ((prop (cdr (assoc (upcase key) alist))) + (value (and prop (plist-get plist prop)))) + (when (stringp value) + (setq plist + (plist-put + plist prop + (org-element-parse-secondary-string + value + (cdr (assq 'keyword org-element-string-restrictions)))))))) + org-element-parsed-keywords)) + ;; Return final value. + plist)) + +(defun org-export-get-global-options (backend) + "Return global export options as a plist. +BACKEND is a symbol specifying which back-end should be used." + (let ((all (append org-export-option-alist + (let ((var (intern + (format "org-%s-option-alist" backend)))) + (and (boundp var) (eval var))))) + ;; Output value. + plist) + (mapc (lambda (cell) + (setq plist + (plist-put plist (car cell) (eval (nth 3 cell))))) + all) + ;; Return value. + plist)) + +(defun org-export-initial-options (options) + "Return a plist with non-optional properties. +OPTIONS is the export options plist computed so far." + (list + :macro-date "(eval (format-time-string \"$1\"))" + :macro-time "(eval (format-time-string \"$1\"))" + :macro-property "(eval (org-entry-get nil \"$1\" 'selective))" + :macro-modification-time + (and (buffer-file-name) + (file-exists-p (buffer-file-name)) + (concat "(eval (format-time-string \"$1\" '" + (prin1-to-string (nth 5 (file-attributes (buffer-file-name)))) + "))")) + :macro-input-file (and (buffer-file-name) + (file-name-nondirectory (buffer-file-name))) + :footnotes-labels-alist + (let (alist) + (org-with-wide-buffer + (goto-char (point-min)) + (while (re-search-forward org-footnote-definition-re nil t) + (let ((def (org-footnote-at-definition-p))) + (org-skip-whitespace) + (push (cons (car def) + (save-restriction + (narrow-to-region (point) (nth 2 def)) + (org-element-parse-buffer))) + alist))) + alist)))) + +(defvar org-export-allow-BIND-local nil) +(defun org-export-confirm-letbind () + "Can we use #+BIND values during export? +By default this will ask for confirmation by the user, to divert +possible security risks." + (cond + ((not org-export-allow-BIND) nil) + ((eq org-export-allow-BIND t) t) + ((local-variable-p 'org-export-allow-BIND-local) org-export-allow-BIND-local) + (t (org-set-local 'org-export-allow-BIND-local + (yes-or-no-p "Allow BIND values in this buffer? "))))) + +(defun org-export-install-letbind-maybe () + "Install the values from #+BIND lines as local variables. +Variables must be installed before in-buffer options are +retrieved." + (let (letbind pair) + (org-with-wide-buffer + (goto-char (point-min)) + (while (re-search-forward (org-make-options-regexp '("BIND")) nil t) + (when (org-export-confirm-letbind) + (push (read (concat "(" (org-match-string-no-properties 2) ")")) + letbind)))) + (while (setq pair (pop letbind)) + (org-set-local (car pair) (nth 1 pair))))) + + +;;;; Persistent Properties + +;; Persistent properties are declared in +;; `org-export-persistent-properties-list' variable. Most of them are +;; initialized at the beginning of the transcoding process by +;; `org-export-initialize-persistent-properties'. The others are +;; updated during that process. + +;; Dedicated functions focus on computing the value of specific +;; persistent properties during initialization. Thus, +;; `org-export-use-select-tag-p' determines if an headline makes use +;; of an export tag enforcing inclusion. `org-export-get-min-level' +;; gets the minimal exportable level, used as a basis to compute +;; relative level for headlines. Eventually, +;; `org-export-get-point-max' returns the maximum exportable ending +;; position in the parse tree. + +(defconst org-export-persistent-properties-list + '(:babel-data-alist :babel-info-alist :babel-results-alist :code-refs + :headline-offset :parse-tree :point-max + :seen-footnote-labels :total-loc :use-select-tags) + "List of persistent properties.") + +(defconst org-export-persistent-properties nil + "Used internally to store properties and values during transcoding. + +Only properties that should survive recursion are saved here. + +This variable is reset before each transcoding.") + +(defun org-export-initialize-persistent-properties (data options backend) + "Initialize `org-export-persistent-properties'. + +DATA is the parse tree from which information is retrieved. +OPTIONS is a list holding export options. BACKEND is the +back-end called for transcoding, as a symbol. + +Following initial persistent properties are set: +`:back-end' Back-end used for transcoding. + +`:headline-alist' Alist of all headlines' name as key and a list + holding beginning and ending positions as + value. + +`:headline-offset' Offset between true level of headlines and + local level. An offset of -1 means an headline + of level 2 should be considered as a level + 1 headline in the context. + +`:parse-tree' Whole parse tree. + +`:point-max' Last position in the parse tree + +`:target-list' List of all targets' raw name in the parse tree. + +`:use-select-tags' Non-nil when parsed tree use a special tag to + enforce transcoding of the headline." + ;; First delete any residual persistent property. + (setq org-export-persistent-properties nil) + ;; Immediately after, set `:use-select-tags' property, as it will be + ;; required for further computations. + (setq options + (org-export-set-property + options + :use-select-tags + (org-export-use-select-tags-p data options))) + ;; Get the rest of the initial persistent properties, now + ;; `:use-select-tags' is set... + ;; 1. `:parse-tree' ... + (setq options (org-export-set-property options :parse-tree data)) + ;; 2. `:headline-offset' ... + (setq options + (org-export-set-property + options :headline-offset + (- 1 (org-export-get-min-level data options)))) + ;; 3. `:point-max' ... + (setq options (org-export-set-property + options :point-max + (org-export-get-point-max data options))) + ;; 4. `:target-list'... + (setq options (org-export-set-property + options :target-list + (org-element-map + data 'target + (lambda (target info) + (org-element-get-property :raw-value target))))) + ;; 5. `:headline-alist' + (setq options (org-export-set-property + options :headline-alist + (org-element-map + data 'headline + (lambda (headline info) + (list (org-element-get-property :raw-value headline) + (org-element-get-property :begin headline) + (org-element-get-property :end headline)))))) + ;; 6. `:back-end' + (setq options (org-export-set-property options :back-end backend))) + +(defun org-export-use-select-tags-p (data options) + "Non-nil when data use a tag enforcing transcoding. +DATA is parsed data as returned by `org-element-parse-buffer'. +OPTIONS is a plist holding export options." + (org-element-map + data + 'headline + (lambda (headline info) + (let ((tags (org-element-get-property :with-tags headline))) + (and tags (string-match + (format ":%s:" (plist-get info :select-tags)) tags)))) + options + 'stop-at-first-match)) + +(defun org-export-get-min-level (data options) + "Return minimum exportable headline's level in DATA. +DATA is parsed tree as returned by `org-element-parse-buffer'. +OPTIONS is a plist holding export options." + (catch 'exit + (let ((min-level 10000)) + (mapc (lambda (blob) + (when (and (eq (car blob) 'headline) + (not (org-export-skip-p blob options))) + (setq min-level + (min (org-element-get-property :level blob) min-level))) + (when (= min-level 1) (throw 'exit 1))) + (org-element-get-contents data)) + ;; If no headline was found, for the sake of consistency, set + ;; minimum level to 1 nonetheless. + (if (= min-level 10000) 1 min-level)))) + +(defun org-export-get-point-max (data options) + "Return last exportable ending position in DATA. +DATA is parsed tree as returned by `org-element-parse-buffer'. +OPTIONS is a plist holding export options." + (let ((pos-max 1)) + (mapc (lambda (blob) + (unless (and (eq (car blob) 'headline) + (org-export-skip-p blob options)) + (setq pos-max (org-element-get-property :end blob)))) + (org-element-get-contents data)) + pos-max)) + + +;;;; Properties Management + +;; This is mostly done with the help of two functions. On the one +;; hand `org-export-update-info' is used to keep up-to-date local +;; information while walking the nested list representing the parsed +;; document. On the other end, `org-export-set-property' handles +;; properties modifications according to their type (persistent or +;; local). + +;; As exceptions, `:code-refs' and `:total-loc' properties are updated +;; with `org-export-handle-code' function. + +(defun org-export-update-info (blob info recursep) + "Update export options depending on context. + +BLOB is the element or object being parsed. INFO is the plist +holding the export options. + +When RECURSEP is non-nil, assume the following element or object +will be inside the current one. + +The following properties are updated: +`genealogy' List of current element's parents + (symbol list). +`inherited-properties' List of inherited properties from + parent headlines (plist). +`parent-properties' List of last element's properties + (plist). +`previous-element' Previous element's type (symbol). +`previous-object' Previous object's type (symbol). +`previous-section-number' Numbering of the previous headline + (vector). +`seen-footnote-labels' List of already parsed footnote + labels (string list) + +Return the property list." + (let* ((type (and (not (stringp blob)) (car blob))) + (relative-level (and (eq type 'headline) + (org-export-get-relative-level blob info))) + (current-num (and (eq type 'headline) + (or (plist-get info :previous-section-number) + (make-vector org-export-max-depth 0))))) + (cond + ;; Case 1: We're moving into a recursive blob. + (recursep + (org-combine-plists + info + `(:genealogy ,(cons type (plist-get info :genealogy)) + :previous-element nil + :previous-object nil + :parent-properties ,(if (memq type org-element-all-elements) + (nth 1 blob) + (plist-get info :parent-properties)) + :inherited-properties + ,(if (eq type 'headline) + (org-combine-plists + (plist-get info :inherited-properties) (nth 1 blob)) + (plist-get info :inherited-properties)) + :previous-section-number + ,(let ((current-num (copy-sequence current-num))) + (if (not (eq type 'headline)) + current-num + (progn (incf (aref current-num (1- relative-level))) + current-num)))) + ;; Add persistent properties. + org-export-persistent-properties)) + ;; Case 2: No recursion. + (t + ;; At a footnote reference: mark its label as seen, if not + ;; already the case. + (when (eq type 'footnote-reference) + (let ((label (org-element-get-property :label blob)) + (seen-labels (plist-get org-export-persistent-properties + :seen-footnote-labels))) + ;; Store anonymous footnotes (nil label) without checking if + ;; another anonymous footnote was seen before. + (unless (and label (member label seen-labels)) + (setq info (org-export-set-property + info :seen-footnote-labels (push label seen-labels)))))) + ;; At an headline: update section number. + (when (eq type 'headline) + (setq info (org-export-set-property + info :previous-section-number + (progn (incf (aref current-num (1- relative-level))) + current-num)))) + ;; Set `:previous-element' or `:previous-object' according to + ;; BLOB. + (setq info (cond ((not type) + (org-export-set-property + info :previous-object 'plain-text)) + ((memq type org-element-all-elements) + (org-export-set-property info :previous-element type)) + (t (org-export-set-property info :previous-object type)))) + ;; Return updated value. + info)))) + +(defun org-export-set-property (info prop value) + "Set property PROP to VALUE in plist INFO. +Return the new plist." + (when (memq prop org-export-persistent-properties-list) + (setq org-export-persistent-properties + (plist-put org-export-persistent-properties prop value))) + (plist-put info prop value)) + + + +;;; The Transcoder + +;; This function reads Org data (obtained with, i.e. +;; `org-element-parse-buffer') and transcodes it into a specified +;; back-end output. It takes care of updating local properties, +;; filtering out elements or objects according to export options and +;; organizing the output blank lines and white space are preserved. + +;; Though, this function is inapropriate for secondary strings, which +;; require a fresh copy of the plist passed as INFO argument. Thus, +;; `org-export-secondary-string' is provided for that specific task. + +;; Internally, three functions handle the filtering of objects and +;; elements during the export. More precisely, `org-export-skip-p' +;; determines if the considered object or element should be ignored +;; altogether, `org-export-interpret-p' tells which elements or +;; objects should be seen as real Org syntax and `org-export-expand' +;; transforms the others back into their original shape. + +(defun org-export-data (data backend info) + "Convert DATA to a string into BACKEND format. + +DATA is a nested list as returned by `org-element-parse-buffer'. + +BACKEND is a symbol among supported exporters. + +INFO is a plist holding export options and also used as +a communication channel between elements when walking the nested +list. See `org-export-update-info' function for more +details. + +Return transcoded string." + (mapconcat + ;; BLOB can be an element, an object, a string, or nil. + (lambda (blob) + (cond + ((not blob) nil) ((equal blob "") nil) + ;; BLOB is a string. Check if the optional transcoder for plain + ;; text exists, and call it in that case. Otherwise, simply + ;; return string. Also update INFO and call + ;; `org-export-filter-plain-text-functions'. + ((stringp blob) + (setq info (org-export-update-info blob info nil)) + (let ((transcoder (intern (format "org-%s-plain-text" backend)))) + (org-export-filter-apply-functions + org-export-filter-plain-text-functions + (if (fboundp transcoder) (funcall transcoder blob info) blob) + backend))) + ;; BLOB is an element or an object. + (t + (let* ((type (if (stringp blob) 'plain-text (car blob))) + ;; 1. Determine the appropriate TRANSCODER. + (transcoder + (cond + ;; 1.0 A full Org document is inserted. + ((eq type 'org-data) 'identity) + ;; 1.1. BLOB should be ignored. + ((org-export-skip-p blob info) nil) + ;; 1.2. BLOB shouldn't be transcoded. Interpret it + ;; back into Org syntax. + ((not (org-export-interpret-p blob info)) + 'org-export-expand) + ;; 1.3. Else apply naming convention. + (t (let ((trans (intern + (format "org-%s-%s" backend type)))) + (and (fboundp trans) trans))))) + ;; 2. Compute CONTENTS of BLOB. + (contents + (cond + ;; Case 0. No transcoder defined: ignore BLOB. + ((not transcoder) nil) + ;; Case 1. Transparently export an Org document. + ((eq type 'org-data) + (org-export-data blob backend info)) + ;; Case 2. For a recursive object. + ((memq type org-element-recursive-objects) + (org-export-data + blob backend (org-export-update-info blob info t))) + ;; Case 3. For a recursive element. + ((memq type org-element-greater-elements) + ;; Ignore contents of an archived tree + ;; when `:with-archived-trees' is `headline'. + (unless (and + (eq type 'headline) + (eq (plist-get info :with-archived-trees) 'headline) + (org-element-get-property :archivedp blob)) + (org-element-normalize-string + (org-export-data + blob backend (org-export-update-info blob info t))))) + ;; Case 4. For a paragraph. + ((eq type 'paragraph) + (let ((paragraph + (org-element-normalize-contents + blob + ;; When normalizing contents of an item or + ;; a footnote definition, ignore first line's + ;; indentation: there is none and it might be + ;; misleading. + (and (not (plist-get info :previous-element)) + (let ((parent (car (plist-get info :genealogy)))) + (memq parent '(footnote-definition item))))))) + (org-export-data + paragraph + backend + (org-export-update-info blob info t)))))) + ;; 3. Transcode BLOB into RESULTS string. + (results (cond + ((not transcoder) nil) + ((eq transcoder 'org-export-expand) + (org-export-data + `(org-data nil ,(funcall transcoder blob contents)) + backend info)) + (t (funcall transcoder blob contents info))))) + ;; 4. Discard nil results. Otherwise, update INFO, append + ;; the same white space between elements or objects as in + ;; the original buffer, and call appropriate filters. + (when results + (setq info (org-export-update-info blob info nil)) + ;; No filter for a full document. + (if (eq type 'org-data) + results + (org-export-filter-apply-functions + (eval (intern (format "org-export-filter-%s-functions" type))) + (if (memq type org-element-all-elements) + (concat + (org-element-normalize-string results) + (make-string (org-element-get-property :post-blank blob) 10)) + (concat + results + (make-string + (org-element-get-property :post-blank blob) 32))) + backend))))))) + (org-element-get-contents data) "")) + +(defun org-export-secondary-string (secondary backend info) + "Convert SECONDARY string into BACKEND format. + +SECONDARY is a nested list as returned by +`org-element-parse-secondary-string'. + +BACKEND is a symbol among supported exporters. + +INFO is a plist holding export options and also used as +a communication channel between elements when walking the nested +list. See `org-export-update-info' function for more +details. + +Return transcoded string." + ;; Make SECONDARY acceptable for `org-export-data'. + (let ((s (if (listp secondary) secondary (list secondary)))) + (org-export-data `(org-data nil ,@s) backend (copy-sequence info)))) + +(defun org-export-skip-p (blob info) + "Non-nil when element or object BLOB should be skipped during export. +INFO is the plist holding export options." + ;; Check headline. + (unless (stringp blob) + (case (car blob) + ('headline + (let ((with-tasks (plist-get info :with-tasks)) + (todo (org-element-get-property :todo-keyword blob)) + (todo-type (org-element-get-property :todo-type blob)) + (archived (plist-get info :with-archived-trees)) + (tag-list (let ((tags (org-element-get-property :tags blob))) + (and tags (org-split-string tags ":"))))) + (or + ;; Ignore subtrees with an exclude tag. + (loop for k in (plist-get info :exclude-tags) + thereis (member k tag-list)) + ;; Ignore subtrees without a select tag, when such tag is found + ;; in the buffer. + (and (plist-get info :use-select-tags) + (loop for k in (plist-get info :select-tags) + never (member k tag-list))) + ;; Ignore commented sub-trees. + (org-element-get-property :commentedp blob) + ;; Ignore archived subtrees if `:with-archived-trees' is nil. + (and (not archived) (org-element-get-property :archivedp blob)) + ;; Ignore tasks, if specified by `:with-tasks' property. + (and todo (not with-tasks)) + (and todo + (memq with-tasks '(todo done)) + (not (eq todo-type with-tasks))) + (and todo + (consp with-tasks) + (not (member todo with-tasks)))))) + ;; Check time-stamp. + ('time-stamp (not (plist-get info :with-timestamps))) + ;; Check drawer. + ('drawer + (or (not (plist-get info :with-drawers)) + (and (consp (plist-get info :with-drawers)) + (not (member (org-element-get-property :drawer-name blob) + (plist-get info :with-drawers)))))) + ;; Check export snippet. + ('export-snippet + (let* ((raw-back-end (org-element-get-property :back-end blob)) + (true-back-end + (or (cdr (assoc raw-back-end org-export-snippet-translation-alist)) + raw-back-end))) + (not (string= (symbol-name (plist-get info :back-end)) + true-back-end))))))) + +(defun org-export-interpret-p (blob info) + "Non-nil if element or object BLOB should be interpreted as Org syntax. +Check is done according to export options INFO, stored as +a plist." + (case (car blob) + ;; ... entities... + (entity (plist-get info :with-entities)) + ;; ... emphasis... + (emphasis (plist-get info :with-emphasize)) + ;; ... fixed-width areas. + (fixed-width (plist-get info :with-fixed-width)) + ;; ... footnotes... + ((footnote-definition footnote-reference) + (plist-get info :with-footnotes)) + ;; ... sub/superscripts... + ((subscript superscript) + (let ((sub/super-p (plist-get info :with-sub-superscript))) + (if (eq sub/super-p '{}) + (org-element-get-property :use-brackets-p blob) + sub/super-p))) + ;; ... tables... + (table (plist-get info :with-tables)) + (otherwise t))) + +(defsubst org-export-expand (blob contents) + "Expand a parsed element or object to its original state. +BLOB is either an element or an object. CONTENTS is its +contents, as a string or nil." + (funcall + (intern (format "org-element-%s-interpreter" (car blob))) blob contents)) + + + +;;; The Filter System + +;; Filters allow end-users to tweak easily the transcoded output. +;; They are the functional counterpart of hooks, as every filter in +;; a set is applied to the return value of the previous one. + +;; Every set is back-end agnostic. Although, a filter is always +;; called, in addition to the string it applies to, with the back-end +;; used as argument, so it's easy enough for the end-user to add +;; back-end specific filters in the set. + +;; Filters sets are defined below. There are of four types: + +;; - `org-export-filter-parse-tree-functions' applies directly on the +;; complete parsed tree. It's the only filters set that doesn't +;; apply to a string. +;; - `org-export-filter-final-output-functions' applies to the final +;; transcoded string. +;; - `org-export-filter-plain-text-functions' applies to any string +;; not recognized as Org syntax. +;; - `org-export-filter-TYPE-functions' applies on the string returned +;; after an element or object of type TYPE has been transcoded. + +;; All filters sets are applied through +;; `org-export-filter-apply-functions' function. Filters in a set are +;; applied in reverse order, that is in the order of consing. It +;; allows developers to be reasonably sure that their filters will be +;; applied first. + +;;;; Special Filters +(defvar org-export-filter-parse-tree-functions nil + "Filter, or list of filters, applied to the parsed tree. +Each filter is called with two arguments: the parse tree, as +returned by `org-element-parse-buffer', and the back-end as +a symbol. It must return the modified parse tree to transcode.") + +(defvar org-export-filter-final-output-functions nil + "Filter, or list of filters, applied to the transcoded string. +Each filter is called with two arguments: the full transcoded +string, and the back-end as a symbol. It must return a string +that will be used as the final export output.") + +(defvar org-export-filter-plain-text-functions nil + "Filter, or list of filters, applied to plain text. +Each filter is called with two arguments: a string which contains +no Org syntax, and the back-end as a symbol. It must return +a string or nil.") + + +;;;; Elements Filters + +(defvar org-export-filter-center-block-functions nil + "Filter, or list of filters, applied to a transcoded center block. +Each filter is called with two arguments: the transcoded center +block, as a string, and the back-end, as a symbol. It must +return a string or nil.") + +(defvar org-export-filter-drawer-functions nil + "Filter, or list of filters, applied to a transcoded drawer. +Each filter is called with two arguments: the transcoded drawer, +as a string, and the back-end, as a symbol. It must return +a string or nil.") + +(defvar org-export-filter-dynamic-block-functions nil + "Filter, or list of filters, applied to a transcoded dynamic-block. +Each filter is called with two arguments: the transcoded +dynamic-block, as a string, and the back-end, as a symbol. It +must return a string or nil.") + +(defvar org-export-filter-headline-functions nil + "Filter, or list of filters, applied to a transcoded headline. +Each filter is called with two arguments: the transcoded +headline, as a string, and the back-end, as a symbol. It must +return a string or nil.") + +(defvar org-export-filter-inlinetask-functions nil + "Filter, or list of filters, applied to a transcoded inlinetask. +Each filter is called with two arguments: the transcoded +inlinetask, as a string, and the back-end, as a symbol. It must +return a string or nil.") + +(defvar org-export-filter-plain-list-functions nil + "Filter, or list of filters, applied to a transcoded plain-list. +Each filter is called with two arguments: the transcoded +plain-list, as a string, and the back-end, as a symbol. It must +return a string or nil.") + +(defvar org-export-filter-item-functions nil + "Filter, or list of filters, applied to a transcoded item. +Each filter is called with two arguments: the transcoded item, as +a string, and the back-end, as a symbol. It must return a string +or nil.") + +(defvar org-export-filter-comment-functions nil + "Filter, or list of filters, applied to a transcoded comment. +Each filter is called with two arguments: the transcoded comment, +as a string, and the back-end, as a symbol. It must return +a string or nil.") + +(defvar org-export-filter-comment-block-functions nil + "Filter, or list of filters, applied to a transcoded comment-comment. +Each filter is called with two arguments: the transcoded +comment-block, as a string, and the back-end, as a symbol. It +must return a string or nil.") + +(defvar org-export-filter-example-block-functions nil + "Filter, or list of filters, applied to a transcoded example-block. +Each filter is called with two arguments: the transcoded +example-block, as a string, and the back-end, as a symbol. It +must return a string or nil.") + +(defvar org-export-filter-export-block-functions nil + "Filter, or list of filters, applied to a transcoded export-block. +Each filter is called with two arguments: the transcoded +export-block, as a string, and the back-end, as a symbol. It +must return a string or nil.") + +(defvar org-export-filter-fixed-width-functions nil + "Filter, or list of filters, applied to a transcoded fixed-width. +Each filter is called with two arguments: the transcoded +fixed-width, as a string, and the back-end, as a symbol. It must +return a string or nil.") + +(defvar org-export-filter-footnote-definition-functions nil + "Filter, or list of filters, applied to a transcoded footnote-definition. +Each filter is called with two arguments: the transcoded +footnote-definition, as a string, and the back-end, as a symbol. +It must return a string or nil.") + +(defvar org-export-filter-horizontal-rule-functions nil + "Filter, or list of filters, applied to a transcoded horizontal-rule. +Each filter is called with two arguments: the transcoded +horizontal-rule, as a string, and the back-end, as a symbol. It +must return a string or nil.") + +(defvar org-export-filter-keyword-functions nil + "Filter, or list of filters, applied to a transcoded keyword. +Each filter is called with two arguments: the transcoded keyword, +as a string, and the back-end, as a symbol. It must return +a string or nil.") + +(defvar org-export-filter-latex-environment-functions nil + "Filter, or list of filters, applied to a transcoded latex-environment. +Each filter is called with two arguments: the transcoded +latex-environment, as a string, and the back-end, as a symbol. +It must return a string or nil.") + +(defvar org-export-filter-babel-call-functions nil + "Filter, or list of filters, applied to a transcoded babel-call. +Each filter is called with two arguments: the transcoded +babel-call, as a string, and the back-end, as a symbol. It must +return a string or nil.") + +(defvar org-export-filter-paragraph-functions nil + "Filter, or list of filters, applied to a transcoded paragraph. +Each filter is called with two arguments: the transcoded +paragraph, as a string, and the back-end, as a symbol. It must +return a string or nil.") + +(defvar org-export-filter-property-drawer-functions nil + "Filter, or list of filters, applied to a transcoded property-drawer. +Each filter is called with two arguments: the transcoded +property-drawer, as a string, and the back-end, as a symbol. It +must return a string or nil.") + +(defvar org-export-filter-quote-block-functions nil + "Filter, or list of filters, applied to a transcoded quote block. +Each filter is called with two arguments: the transcoded quote +block, as a string, and the back-end, as a symbol. It must +return a string or nil.") + +(defvar org-export-filter-quote-section-functions nil + "Filter, or list of filters, applied to a transcoded quote-section. +Each filter is called with two arguments: the transcoded +quote-section, as a string, and the back-end, as a symbol. It +must return a string or nil.") + +(defvar org-export-filter-special-block-functions nil + "Filter, or list of filters, applied to a transcoded special block. +Each filter is called with two arguments: the transcoded special +block, as a string, and the back-end, as a symbol. It must +return a string or nil.") + +(defvar org-export-filter-src-block-functions nil + "Filter, or list of filters, applied to a transcoded src-block. +Each filter is called with two arguments: the transcoded +src-block, as a string, and the back-end, as a symbol. It must +return a string or nil.") + +(defvar org-export-filter-table-functions nil + "Filter, or list of filters, applied to a transcoded table. +Each filter is called with two arguments: the transcoded table, +as a string, and the back-end, as a symbol. It must return +a string or nil.") + +(defvar org-export-filter-verse-block-functions nil + "Filter, or list of filters, applied to a transcoded verse block. +Each filter is called with two arguments: the transcoded verse +block, as a string, and the back-end, as a symbol. It must +return a string or nil.") + + +;;;; Objects Filters + +(defvar org-export-filter-emphasis-functions nil + "Filter, or list of filters, applied to a transcoded emphasis. +Each filter is called with two arguments: the transcoded +emphasis, as a string, and the back-end, as a symbol. It must +return a string or nil.") + +(defvar org-export-filter-entity-functions nil + "Filter, or list of filters, applied to a transcoded entity. +Each filter is called with two arguments: the transcoded entity, +as a string, and the back-end, as a symbol. It must return +a string or nil.") + +(defvar org-export-filter-export-snippet-functions nil + "Filter, or list of filters, applied to a transcoded export-snippet. +Each filter is called with two arguments: the transcoded +export-snippet, as a string, and the back-end, as a symbol. It +must return a string or nil.") + +(defvar org-export-filter-footnote-reference-functions nil + "Filter, or list of filters, applied to a transcoded footnote-reference. +Each filter is called with two arguments: the transcoded +footnote-reference, as a string, and the back-end, as a symbol. +It must return a string or nil.") + +(defvar org-export-filter-inline-babel-call-functions nil + "Filter, or list of filters, applied to a transcoded inline-babel-call. +Each filter is called with two arguments: the transcoded +inline-babel-call, as a string, and the back-end, as a symbol. It +must return a string or nil.") + +(defvar org-export-filter-inline-src-block-functions nil + "Filter, or list of filters, applied to a transcoded inline-src-block. +Each filter is called with two arguments: the transcoded +inline-src-block, as a string, and the back-end, as a symbol. It +must return a string or nil.") + +(defvar org-export-filter-latex-fragment-functions nil + "Filter, or list of filters, applied to a transcoded latex-fragment. +Each filter is called with two arguments: the transcoded +latex-fragment, as a string, and the back-end, as a symbol. It +must return a string or nil.") + +(defvar org-export-filter-line-break-functions nil + "Filter, or list of filters, applied to a transcoded line-break. +Each filter is called with two arguments: the transcoded +line-break, as a string, and the back-end, as a symbol. It must +return a string or nil.") + +(defvar org-export-filter-link-functions nil + "Filter, or list of filters, applied to a transcoded link. +Each filter is called with two arguments: the transcoded link, as +a string, and the back-end, as a symbol. It must return a string +or nil.") + +(defvar org-export-filter-macro-functions nil + "Filter, or list of filters, applied to a transcoded macro. +Each filter is called with two arguments: the transcoded macro, +as a string, and the back-end, as a symbol. It must return +a string or nil.") + +(defvar org-export-filter-radio-target-functions nil + "Filter, or list of filters, applied to a transcoded radio-target. +Each filter is called with two arguments: the transcoded +radio-target, as a string, and the back-end, as a symbol. It +must return a string or nil.") + +(defvar org-export-filter-statistics-cookie-functions nil + "Filter, or list of filters, applied to a transcoded statistics-cookie. +Each filter is called with two arguments: the transcoded +statistics-cookie, as a string, and the back-end, as a symbol. +It must return a string or nil.") + +(defvar org-export-filter-subscript-functions nil + "Filter, or list of filters, applied to a transcoded subscript. +Each filter is called with two arguments: the transcoded +subscript, as a string, and the back-end, as a symbol. It must +return a string or nil.") + +(defvar org-export-filter-superscript-functions nil + "Filter, or list of filters, applied to a transcoded superscript. +Each filter is called with two arguments: the transcoded +superscript, as a string, and the back-end, as a symbol. It must +return a string or nil.") + +(defvar org-export-filter-target-functions nil + "Filter, or list of filters, applied to a transcoded target. +Each filter is called with two arguments: the transcoded target, +as a string, and the back-end, as a symbol. It must return +a string or nil.") + +(defvar org-export-filter-time-stamp-functions nil + "Filter, or list of filters, applied to a transcoded time-stamp. +Each filter is called with two arguments: the transcoded +time-stamp, as a string, and the back-end, as a symbol. It must +return a string or nil.") + +(defvar org-export-filter-verbatim-functions nil + "Filter, or list of filters, applied to a transcoded verbatim. +Each filter is called with two arguments: the transcoded +verbatim, as a string, and the back-end, as a symbol. It must +return a string or nil.") + +(defun org-export-filter-apply-functions (filters value backend) + "Call every function in FILTERS with arguments VALUE and BACKEND. +Functions are called in reverse order, to be reasonably sure that +developer-specified filters, if any, are called first." + ;; Ensure FILTERS is a list. + (let ((filters (if (listp filters) (reverse filters) (list filters)))) + (loop for filter in filters + if (not value) return nil else + do (setq value (funcall filter value backend)))) + value) + + + +;;; Core functions + +;; This is the room for the main function, `org-export-as', along with +;; its derivative, `org-export-to-buffer'. They differ only by the +;; way they output the resulting code. + +;; Note that `org-export-as' doesn't really parse the current buffer, +;; but a copy of it (with the same buffer-local variables and +;; visibility), where Babel blocks are executed, if appropriate. +;; `org-export-with-current-buffer-copy' macro prepares that copy. + +(defun org-export-as (backend + &optional subtreep visible-only body-only ext-plist) + "Transcode current Org buffer into BACKEND code. + +If narrowing is active in the current buffer, only transcode its +narrowed part. + +If a region is active, transcode that region. + +When optional argument SUBTREEP is non-nil, transcode the +sub-tree at point, extracting information from the headline +properties first. + +When optional argument VISIBLE-ONLY is non-nil, don't export +contents of hidden elements. + +When optional argument BODY-ONLY is non-nil, only return body +code, without preamble nor postamble. + +EXT-PLIST, when provided, is a property list with external +parameters overriding Org default settings, but still inferior to +file-local settings. + +Return code as a string." + (save-excursion + (save-restriction + ;; Narrow buffer to an appropriate region for parsing. + (when (org-region-active-p) + (narrow-to-region (region-beginning) (region-end))) + (goto-char (point-min)) + (when subtreep + (unless (org-at-heading-p) + (org-with-limited-levels (outline-next-heading))) + (let ((end (save-excursion (org-end-of-subtree t))) + (begin (progn (forward-line) + (org-skip-whitespace) + (point-at-bol)))) + (narrow-to-region begin end))) + ;; Retrieve export options (INFO) and parsed tree (RAW-DATA). + ;; Buffer isn't parsed directly. Instead, a temporary copy is + ;; created, where all code blocks are evaluated. RAW-DATA is + ;; the parsed tree of the buffer resulting from that process. + ;; Eventually call `org-export-filter-parse-tree-functions'.. + (let ((info (org-export-collect-options backend subtreep ext-plist)) + (raw-data (org-export-filter-apply-functions + org-export-filter-parse-tree-functions + (org-export-with-current-buffer-copy + (org-export-blocks-preprocess) + (org-element-parse-buffer nil visible-only)) + backend))) + ;; Initialize the communication system and combine it to INFO. + (setq info + (org-combine-plists + info + (org-export-initialize-persistent-properties + raw-data info backend))) + ;; Now transcode RAW-DATA. Also call + ;; `org-export-filter-final-output-functions'. + (let ((body (org-element-normalize-string + (org-export-data raw-data backend info))) + (template (intern (format "org-%s-template" backend)))) + (if (and (not body-only) (fboundp template)) + (org-trim + (org-export-filter-apply-functions + org-export-filter-final-output-functions + (funcall template body info) + backend)) + (org-export-filter-apply-functions + org-export-filter-final-output-functions body backend))))))) + +(defun org-export-to-buffer (backend buffer &optional subtreep visible-only body-only ext-plist) + "Call `org-export-as' with output to a specified buffer. + +BACKEND is the back-end used for transcoding, as a symbol. + +BUFFER is the output buffer. If it already exists, it will be +erased first, otherwise, it will be created. + +Arguments SUBTREEP, VISIBLE-ONLY and BODY-ONLY are similar to +those used in `org-export-as'. + +EXT-PLIST, when provided, is a property list with external +parameters overriding Org default settings, but still inferior to +file-local settings. + +Return buffer." + (let ((out (org-export-as backend subtreep visible-only body-only ext-plist)) + (buffer (get-buffer-create buffer))) + (with-current-buffer buffer + (erase-buffer) + (insert out) + (goto-char (point-min))) + buffer)) + +(defmacro org-export-with-current-buffer-copy (&rest body) + "Apply BODY in a copy of the current buffer. + +The copy preserves local variables and visibility of the original +buffer. + +Point is at buffer's beginning when BODY is applied." + (org-with-gensyms (original-buffer offset buffer-string overlays) + `(let ((,original-buffer ,(current-buffer)) + (,offset ,(1- (point-min))) + (,buffer-string ,(buffer-string)) + (,overlays (mapcar + 'copy-overlay (overlays-in (point-min) (point-max))))) + (with-temp-buffer + (org-clone-local-variables + ,original-buffer "^\\(org-\\|orgtbl-\\|major-mode$\\)") + (insert ,buffer-string) + (mapc (lambda (ov) + (move-overlay + ov + (- (overlay-start ov) ,offset) + (- (overlay-end ov) ,offset) + (current-buffer))) + ,overlays) + (goto-char (point-min)) + (progn ,@body))))) +(def-edebug-spec org-export-with-current-buffer-copy (body)) + + + +;;; Tools For Back-Ends + +;; A whole set of tools is available to help build new exporters. Any +;; function general enough to have its use across many back-ends +;; should be added here. + +;; As of now, functions operating on headlines, include keywords, +;; links, macros, src-blocks, tables and tables of contents are +;; implemented. + + +;;;; For Headlines + +;; `org-export-get-relative-level' is a shortcut to get headline +;; level, relatively to the lower headline level in the parsed tree. + +;; `org-export-get-headline-number' returns the section number of an +;; headline, while `org-export-number-to-roman' allows to convert it +;; to roman numbers. + +;; `org-export-first-sibling-p' and `org-export-last-sibling-p' are +;; two useful predicates when it comes to fulfill the +;; `:headline-levels' property. + +(defun org-export-get-relative-level (headline info) + "Return HEADLINE relative level within current parsed tree. +INFO is a plist holding contextual information." + (+ (org-element-get-property :level headline) + (or (plist-get info :headline-offset) 0))) + +(defun org-export-get-headline-number (headline info) + "Return HEADLINE numbering as a list of numbers. +INFO is a plist holding contextual information." + (let ((relative-level (org-export-get-relative-level headline info)) + (previous-numbering (or (plist-get info :previous-section-number) + (make-vector org-export-max-depth 0)))) + (loop for n across previous-numbering + for i from 1 to relative-level + collect (if (= i relative-level) (1+ n) n)))) + +(defun org-export-number-to-roman (n) + "Convert integer N into a roman numeral." + (let ((roman '((1000 . "M") (900 . "CM") (500 . "D") (400 . "CD") + ( 100 . "C") ( 90 . "XC") ( 50 . "L") ( 40 . "XL") + ( 10 . "X") ( 9 . "IX") ( 5 . "V") ( 4 . "IV") + ( 1 . "I"))) + (res "")) + (if (<= n 0) + (number-to-string n) + (while roman + (if (>= n (caar roman)) + (setq n (- n (caar roman)) + res (concat res (cdar roman))) + (pop roman))) + res))) + +(defun org-export-first-sibling-p (headline info) + "Non-nil when HEADLINE is the first sibling in its sub-tree. +INFO is the plist used as a communication channel." + (not (eq (plist-get info :previous-element) 'headline))) + +(defun org-export-last-sibling-p (headline info) + "Non-nil when HEADLINE is the last sibling in its sub-tree. +INFO is the plist used as a communication channel." + (= (org-element-get-property :end headline) + (or (plist-get (plist-get info :parent-properties) :end) + (plist-get info :point-max)))) + + +;;;; For Include Keywords + +;; This section provides a tool to properly handle insertion of files +;; during export: `org-export-included-files'. It recursively +;; transcodes a file specfied by an include keyword. + +;; It uses two helper functions: `org-export-get-file-contents' +;; returns contents of a file according to parameters specified in the +;; keyword while `org-export-parse-included-file' parses the file +;; specified by it. + +(defun org-export-included-file (keyword backend info) + "Transcode file specified with include KEYWORD. + +KEYWORD is the include keyword element transcoded. BACKEND is +the language back-end used for transcoding. INFO is the plist +used as a communication channel. + +This function updates `:included-files' and `:headline-offset' +properties. + +Return the transcoded string." + (let ((data (org-export-parse-included-file keyword info)) + (file (let ((value (org-element-get-property :value keyword))) + (and (string-match "^\"\\(\\S-+\\)\"" value) + (match-string 1 value))))) + (org-element-normalize-string + (org-export-data + data backend + (org-combine-plists + info + ;; Store full path of already included files to avoid + ;; recursive file inclusion. + `(:included-files + ,(cons (expand-file-name file) (plist-get info :included-files)) + ;; Ensure that a top-level headline in the included + ;; file becomes a direct child of the current headline + ;; in the buffer. + :headline-offset + ,(- (+ (plist-get (plist-get info :inherited-properties) :level) + (plist-get info :headline-offset)) + (1- (org-export-get-min-level data info))))))))) + +(defun org-export-get-file-contents (file &optional lines) + "Get the contents of FILE and return them as a string. +When optional argument LINES is a string specifying a range of +lines, include only those lines." + (with-temp-buffer + (insert-file-contents file) + (when lines + (let* ((lines (split-string lines "-")) + (lbeg (string-to-number (car lines))) + (lend (string-to-number (cadr lines))) + (beg (if (zerop lbeg) (point-min) + (goto-char (point-min)) + (forward-line (1- lbeg)) + (point))) + (end (if (zerop lend) (point-max) + (goto-char (point-min)) + (forward-line (1- lend)) + (point)))) + (narrow-to-region beg end))) + (buffer-string))) + +(defun org-export-parse-included-file (keyword info) + "Parse file specified by include KEYWORD. + +KEYWORD is the include keyword element transcoded. BACKEND is the +language back-end used for transcoding. INFO is the plist used as +a communication channel. + +Return the parsed tree." + (let* ((value (org-element-get-property :value keyword)) + (file (and (string-match "^\"\\(\\S-+\\)\"" value) + (prog1 (match-string 1 value) + (setq value (replace-match "" nil nil value))))) + (lines (and (string-match + ":lines +\"\\(\\(?:[0-9]+\\)?-\\(?:[0-9]+\\)?\\)\"" value) + (prog1 (match-string 1 value) + (setq value (replace-match "" nil nil value))))) + (env (cond ((string-match "\\" value) "example") + ((string-match "\\ link-begin (nth 1 head)) + (<= link-end (nth 2 head))) + collect (cons (nth 1 head) (nth 2 head))))) + (loop named main for ancestor in (nreverse ancestors) do + (loop for candidate in cands + when (and (>= (car candidate) (car ancestor)) + (<= (cdr candidate) (cdr ancestor))) + do (return-from main (car candidate)))))) + ;; No candidate have a common ancestor with link: First match + ;; will do. Return its beginning position. + (t (caar cands))))))) + + +;;;; For Macros + +;; `org-export-expand-macro' simply takes care of expanding macros. + +(defun org-export-expand-macro (macro info) + "Expand MACRO and return it as a string. +INFO is a plist holding export options." + (let* ((key (org-element-get-property :key macro)) + (args (org-element-get-property :args macro)) + (value (plist-get info (intern (format ":macro-%s" key))))) + ;; Replace arguments in VALUE. + (let ((s 0) n) + (while (string-match "\\$\\([0-9]+\\)" value s) + (setq s (1+ (match-beginning 0)) + n (string-to-number (match-string 1 value))) + (and (>= (length args) n) + (setq value (replace-match (nth (1- n) args) t t value))))) + ;; VALUE starts with "(eval": it is a s-exp, `eval' it. + (when (string-match "\\`(eval\\>" value) + (setq value (eval (read value)))) + ;; Return expanded string. + (format "%s" value))) + + +;;;; For Src-Blocks + +;; `org-export-handle-code' takes care of line numbering and reference +;; cleaning in source code, when appropriate. It also updates global +;; LOC count (`:total-loc' property) and code references alist +;; (`:code-refs' property). + +(defun org-export-handle-code (code switches info + &optional language num-fmt ref-fmt) + "Handle line numbers and code references in CODE. + +CODE is the string to process. SWITCHES is the option string +determining which changes will be applied to CODE. INFO is the +plist used as a communication channel during export. + +Optional argument LANGUAGE, when non-nil, is a string specifying +code's language. + +If optional argument NUM-FMT is a string, it will be used as +a format string for numbers at beginning of each line. + +If optional argument REF-FMT is a string, it will be used as +a format string for each line of code containing a reference. + +Update the following INFO properties by side-effect: `:total-loc' +and `:code-refs'. + +Return new code as a string." + (let* ((switches (or switches "")) + (numberp (string-match "[-+]n\\>" switches)) + (continuep (string-match "\\+n\\>" switches)) + (total-LOC (if (and numberp (not continuep)) + 0 + (or (plist-get info :total-loc) 0))) + (preserve-indent-p (or org-src-preserve-indentation + (string-match "-i\\>" switches))) + (replace-labels (when (string-match "-r\\>" switches) + (if (string-match "-k\\>" switches) 'keep t))) + ;; Get code and clean it. Remove blank lines at its + ;; beginning and end. Also remove protective commas. + (code (let ((c (replace-regexp-in-string + "\\`\\([ \t]*\n\\)+" "" + (replace-regexp-in-string + "\\(:?[ \t]*\n\\)*[ \t]*\\'" "\n" code)))) + ;; If appropriate, remove global indentation. + (unless preserve-indent-p (setq c (org-remove-indentation c))) + ;; Free up the protected lines. Note: Org blocks + ;; have commas at the beginning or every line. + (if (string= language "org") + (replace-regexp-in-string "^," "" c) + (replace-regexp-in-string + "^\\(,\\)\\(:?\\*\\|[ \t]*#\\+\\)" "" c nil nil 1)))) + ;; Split code to process it line by line. + (code-lines (org-split-string code "\n")) + ;; Ensure line numbers will be correctly padded before + ;; applying the format string. + (num-fmt (format (if (stringp num-fmt) num-fmt "%s: ") + (format "%%%ds" + (length (number-to-string + (+ (length code-lines) + total-LOC)))))) + ;; Get format used for references. + (label-fmt (or (and (string-match "-l +\"\\([^\"\n]+\\)\"" switches) + (match-string 1 switches)) + org-coderef-label-format)) + ;; Build a regexp matching a loc with a reference. + (with-ref-re (format "^.*?\\S-.*?\\([ \t]*\\(%s\\)\\)[ \t]*$" + (replace-regexp-in-string + "%s" "\\([-a-zA-Z0-9_ ]+\\)" label-fmt nil t))) + coderefs) + (org-element-normalize-string + (mapconcat (lambda (loc) + ;; Maybe add line number to current line of code + ;; (LOC). + (when numberp + (setq loc (concat (format num-fmt (incf total-LOC)) loc))) + ;; Take action if at a ref line. + (when (string-match with-ref-re loc) + (let ((ref (match-string 3 loc))) + (setq loc + (cond + ;; Option "-k": don't remove labels. Use + ;; numbers for references when lines are + ;; numbered, use labels otherwise. + ((eq replace-labels 'keep) + (let ((full-ref (format "(%s)" ref))) + (push (cons ref (if numberp total-LOC full-ref)) + coderefs) + (replace-match full-ref nil nil loc 2)) + (replace-match (format "(%s)" ref) nil nil loc 2)) + ;; Option "-r" without "-k": remove labels. + ;; Use numbers for references when lines are + ;; numbered, use labels otherwise. + (replace-labels + (push (cons ref (if numberp total-LOC ref)) + coderefs) + (replace-match "" nil nil loc 1)) + ;; Else: don't remove labels and don't use + ;; numbers for references. + (t + (let ((full-ref (format "(%s)" ref))) + (push (cons ref full-ref) coderefs) + (replace-match full-ref nil nil loc 2))))))) + ;; If REF-FMT is defined, apply it to current LOC. + (when (stringp ref-fmt) (setq loc (format ref-fmt loc))) + ;; Update by side-effect communication channel. + ;; Return updated LOC. + (setq info (org-export-set-property + (org-export-set-property + info :code-refs coderefs) + :total-loc total-LOC)) + loc) + code-lines "\n")))) + + +;;;; For Tables + +;; `org-export-table-format-info' extracts formatting information +;; (alignment, column groups and presence of a special column) from +;; a raw table and returns it as a property list. +;; +;; `org-export-clean-table' cleans the raw table from any Org +;; table-specific syntax. + +(defun org-export-table-format-info (table) + "Extract info from TABLE. +Return a plist whose properties and values are: +`:alignment' vector of strings among \"r\", \"l\" and \"c\", +`:column-groups' vector of symbols among `start', `end', `startend', +`:special-column-p' boolean." + (with-temp-buffer + (insert table) + (goto-char 1) + (org-table-align) + (let ((align (vconcat (mapcar (lambda (c) (if c "r" "l")) + org-table-last-alignment))) + (colgroups (make-vector (length org-table-last-alignment) nil)) + (special-column-p 'empty)) + (mapc (lambda (row) + ;; Determine if a special column is present by looking + ;; for special markers in the first column. More + ;; accurately, the first column is considered special if + ;; it only contains special markers and, maybe, empty + ;; cells. + (unless (string-match "^[ \t]*|[-+]+|[ \t]*$" row) + (setq special-column-p + (cond + ((not special-column-p) nil) + ((string-match "^[ \t]*| *\\\\?\\([\#!$*_^]\\) *|" + row) 'special) + ((string-match "^[ \t]*| +|" row) special-column-p)))) + (cond + ((org-table-cookie-line-p row) + ;; Read forced alignment information, if any, and + ;; determine final alignment for the table. + (let ((col 0)) + (mapc (lambda (field) + (when (string-match "<\\([lrc]\\)[0-9]*>" field) + (aset align col (match-string 1 field))) + (incf col)) + (org-split-string row "[ \t]*|[ \t]*")))) + ;; Read column groups information. + ((org-table-colgroup-line-p row) + (let ((col 0)) + (mapc (lambda (field) + (aset colgroups col + (cond ((string= "<" field) 'start) + ((string= ">" field) 'end) + ((string= "<>" field) 'start-end))) + (incf col)) + (org-split-string row "[ \t]*|[ \t]*")))))) + (org-split-string table "\n")) + ;; Return plist. + (list :alignment align + :column-groups colgroups + :special-column-p (eq special-column-p 'special))))) + +(defun org-export-clean-table (table specialp) + "Clean string TABLE from its formatting elements. +Remove any row containing column groups or formatting cookies and +rows starting with a special marker. If SPECIALP is non-nil, +assume the table contains a special formatting column and remove +it also." + (let ((rows (org-split-string table "\n"))) + (mapconcat 'identity + (delq nil + (mapcar + (lambda (row) + (cond + ((org-table-colgroup-line-p row) nil) + ((org-table-cookie-line-p row) nil) + ;; Ignore rows starting with a special marker. + ((string-match "^[ \t]*| *[!_^/] *|" row) nil) + ;; Remove special column. + ((and specialp + (or (string-match "^\\([ \t]*\\)|-+\\+" row) + (string-match "^\\([ \t]*\\)|[^|]*|" row))) + (replace-match "\\1|" t nil row)) + (t row))) + rows)) + "\n"))) + + +;;;; For Tables Of Contents + +;; `org-export-get-headlines' builds a table of contents in the shape +;; of a nested list of cons cells whose car is headline's name and cdr +;; an unique identifier. One can then easily parse it and transcode +;; it in a back-end. Identifiers can be used to construct internal +;; links. + +;; Building lists of tables, figures or listings is quite similar. +;; Once the generic function `org-export-collect-elements' is defined, +;; `org-export-collect-tables', `org-export-collect-figures' and +;; `org-export-collect-listings' can be derived from it. + +(defun org-export-get-headlines (backend info &optional n) + "Build a table of contents. + +BACKEND is the back-end used to transcode headline's name. INFO +is a plist holding export options. + +When non-nil, optional argument N must be an integer. It +specifies the depth of the table of contents. + +Return an alist whose keys are headlines' name and value their +relative level and an unique identifier that might be used for +internal links. + +For example, on the following tree, where numbers in parens are +buffer position at beginning of the line: + +* Title 1 (1) +** Sub-title 1 (21) +** Sub-title 2 (42) +* Title 2 (62) + +the function will return: + +\(\(\"Title 1\" 1 1\) + \(\"Sub-title 1\" 2 21\) + \(\"Sub-title 2\" 2 42\) + \(\"Title 2\" 1 62\)\)" + (org-element-map + (plist-get info :parse-tree) + 'headline + (lambda (headline local-info) + ;; Get HEADLINE's relative level. + (let ((level (+ (or (plist-get local-info :headline-offset) 0) + (org-element-get-property :level headline)))) + (unless (and (wholenump n) (> level n)) + (list + (org-export-secondary-string + (org-element-get-property :title headline) backend info) + level + (org-element-get-property :begin headline))))) + info)) + +(defun org-export-collect-elements (type backend info) + "Collect named elements of type TYPE. + +Only elements with a caption or a name are collected. + +BACKEND is the back-end used to transcode their caption or name. +INFO is a plist holding export options. + +Return an alist where key is entry's name and value an unique +identifier that might be used for internal links." + (org-element-map + (plist-get info :parse-tree) + type + (lambda (element info) + (let ((entry + (cond + ((org-element-get-property :caption element) + (org-export-secondary-string + (org-element-get-property :caption element) backend info)) + ((org-element-get-property :name element) + (org-export-secondary-string + (org-element-get-property :name element) backend info))))) + ;; Skip elements with neither a caption nor a name. + (when entry (cons entry (org-element-get-property :begin element))))) + info)) + +(defun org-export-collect-tables (backend info) + "Build a list of tables. + +BACKEND is the back-end used to transcode table's name. INFO is +a plist holding export options. + +Return an alist where key is the caption of the table and value +an unique identifier that might be used for internal links." + (org-export-collect-elements 'table backend info)) + +(defun org-export-get-figures (backend info) + "Build a list of figures. + +A figure is a paragraph type element with a caption or a name. + +BACKEND is the back-end used to transcode headline's name. INFO +is a plist holding export options. + +Return an alist where key is the caption of the figure and value +an unique indentifier that might be used for internal links." + (org-export-collect-elements 'paragraph backend info)) + +(defun org-export-collect-listings (backend info) + "Build a list of src blocks. + +BACKEND is the back-end used to transcode src block's name. INFO +is a plist holding export options. + +Return an alist where key is the caption of the src block and +value an unique indentifier that might be used for internal +links." + (org-export-collect-elements 'src-block backend info)) + + +(provide 'org-export) +;;; org-export.el ends here